From 67dca0dee3c606463b7693b272a878d318825904 Mon Sep 17 00:00:00 2001 From: samussiah Date: Mon, 13 May 2019 16:53:10 -0400 Subject: [PATCH 1/8] get branches and versions from GitHub API --- build/cat.js | 3127 ++++++++--------- package.json | 4 +- src/cat/controls/initRendererSelect.js | 10 +- .../initRendererSelect/getVersions.js | 22 + 4 files changed, 1437 insertions(+), 1726 deletions(-) create mode 100644 src/cat/controls/initRendererSelect/getVersions.js diff --git a/build/cat.js b/build/cat.js index f31cdb9..133ca0e 100644 --- a/build/cat.js +++ b/build/cat.js @@ -1,1298 +1,1090 @@ -(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 (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); + }); + } + ); + } - function noop() {} + // Store setTimeout reference so promise-polyfill will be unaffected by + // other code modifying setTimeout (like sinon.useFakeTimers()) + var setTimeoutFunc = setTimeout; - // Polyfill for Function.prototype.bind - function bind(fn, thisArg) { - return function() { - fn.apply(thisArg, arguments); - }; - } + function noop() {} - /** - * @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); + // 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; } - - 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); - }); + if (self._state === 0) { + self._deferreds.push(deferred); + return; } - - 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); + 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; } + } + 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]); + } + + 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); } - 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; + for (var i = 0, len = self._deferreds.length; i < len; i++) { + handle(self, self._deferreds[i]); } - - /** - * 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); + 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); + Promise$1.prototype['catch'] = function(onRejected) { + return this.then(null, onRejected); + }; - 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]); - } - }); - }; + Promise$1.prototype.then = function(onFulfilled, onRejected) { + // @ts-ignore + var prom = new this.constructor(noop); - Promise$1.resolve = function(value) { - if (value && typeof value === 'object' && value.constructor === Promise$1) { - return value; - } + handle(this, new Handler(onFulfilled, onRejected, prom)); + return prom; + }; - return new Promise$1(function(resolve) { - resolve(value); - }); - }; + Promise$1.prototype['finally'] = finallyConstructor; - Promise$1.reject = function(value) { - return new Promise$1(function(resolve, reject) { - reject(value); - }); - }; + 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; - 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); + 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; } - }); - }; - - // 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 - } - }; - - /** @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; + } + args[i] = val; + if (--remaining === 0) { + resolve(args); + } + } catch (ex) { + reject(ex); } - 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; - } + for (var i = 0; i < args.length; i++) { + res(i, args[i]); + } + }); + }; - 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 - }); + Promise$1.resolve = function(value) { + if (value && typeof value === 'object' && value.constructor === Promise$1) { + return 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 (!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; - } + 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 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(); - }); + Promise$1._unhandledRejectionFn = function _unhandledRejectionFn(err) { + if (typeof console !== 'undefined' && console) { + console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console } - - 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; + }; + + /** @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 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; + if (typeof window !== 'undefined') { + return window; } - - 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 = ""; - } - - /* 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; + 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 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; - }); + var to = Object(target); - 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 index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; - 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); - }); - } - } - - 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); - } - } + 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]; + } + } + } + } - 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); - } - }); - } + 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'); + } - function loadLibrary(cat) { - var version = cat.controls.libraryVersion.node().value; - var library = 'webcharts'; //hardcode to webcharts for now - could generalize later + var o = Object(this); - // --- load css --- // - var cssPath = - version !== 'master' - ? cat.config.rootURL + '/Webcharts@' + version + '/css/webcharts.css' - : cat.config.rootURL + '/Webcharts/css/webcharts.css'; + // 2. Let len be ? ToLength(? Get(O, 'length')). + var len = o.length >>> 0; - 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); - } + // 3. If IsCallable(predicate) is false, throw a TypeError exception. + if (typeof predicate !== 'function') { + throw new TypeError('predicate must be a function'); + } - // --- load js --- // - var rendererPath = - version !== 'master' - ? cat.config.rootURL + '/' + library + '@' + version + '/build/webcharts.js' - : cat.config.rootURL + '/Webcharts/build/webcharts.js'; + // 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++; + } - 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); - }, - failure: function failure() { - cat.status.loadStatus(cat.statusDiv, false, rendererPath, library, version); - } - }); - } else { - cat.status.loadStatus(cat.statusDiv, true, rendererPath, library, version); - loadRenderer(cat); - } - } + // 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'); + } - 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); - }); - } + var o = Object(this); - function initSubmit(cat) { - addControlsToggle.call(cat); - addSubmitButton.call(cat); - } + // 2. Let len be ? ToLength(? Get(O, "length")). + var len = o.length >>> 0; - function updateRenderer(select) { - var _this = this; + // 3. If IsCallable(predicate) is false, throw a TypeError exception. + if (typeof predicate !== 'function') { + throw new TypeError('predicate must be a function'); + } - this.current = d3 - .select(select) - .select('option:checked') - .data()[0]; - this.current.version = 'master'; + // 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++; + } - //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; + // 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; + } + }; + + 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; + } + + 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 = ""; + } + + /* 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(); + } + + 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); + } - //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; - }); + cat.current.htmlExport = createChartExport(cat); // save the source code before init - //Re-initialize the chart config section - this.settings.set(this); - } + try { + myChart.init(data); + } catch (err) { + cat.status.chartInitStatus(cat.statusDiv, false, err); + } finally { + cat.status.chartInitStatus(cat.statusDiv, true, null, cat.current.htmlExport); - 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); - } + // save to server button + if (cat.config.useServer) { + cat.status.saveToServer(cat); + } + showEnv(cat); - function showDataPreview(cat) { - cat.dataWrap.classed('hidden', false); - cat.chartWrap.classed('hidden', true); - cat.dataWrap.selectAll('*').remove(); + //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); + }); + } + } + + 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); + } + }); + } + + function loadLibrary(cat) { + var version = cat.controls.libraryVersion.node().value; + var library = 'webcharts'; //hardcode to webcharts for now - could generalize later + + // --- load css --- // + var cssPath = version !== 'master' ? cat.config.rootURL + '/Webcharts@' + version + '/css/webcharts.css' : cat.config.rootURL + '/Webcharts/css/webcharts.css'; + + 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); + } + + // --- load js --- // + var rendererPath = version !== 'master' ? cat.config.rootURL + '/' + library + '@' + version + '/build/webcharts.js' : cat.config.rootURL + '/Webcharts/build/webcharts.js'; + + 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); + }, + failure: function failure() { + cat.status.loadStatus(cat.statusDiv, false, rendererPath, library, version); + } + }); + } else { + cat.status.loadStatus(cat.statusDiv, true, rendererPath, library, version); + loadRenderer(cat); + } + } + + 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); + }); + } + + function initSubmit(cat) { + addControlsToggle.call(cat); + addSubmitButton.call(cat); + } + + 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 getVersions(select) { + var repo = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'https://www.github.com/RhoInc/Webcharts'; + + console.log(this); + console.log(select); + console.log(repo); + var api = repo.replace('www.github.com', 'api.github.com/repos'); + var branches = fetch(api + '/branches').then(function (response) { + return response.json(); + }); + var releases = fetch(api + '/releases').then(function (response) { + return response.json(); + }); + + Promise.all([branches, releases]).then(function (values) { + select.selectAll('option').data(d3.merge(values)).enter().append('option').text(function (d) { + return d.name; + }); + }); + } + + 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('select'); + getVersions(cat.controls.versionSelect, cat.current.url); + //cat.controls.versionSelect.node().value = 'master'; + cat.controls.versionSelect.on('input', 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').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; + }); + } + + 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); - if (cat.dataPreview) { - cat.dataPreview.destroy(); - } + //add it to the select dropdown + cat.controls.dataFileSelect.append('option').datum(dataObject).text(function (d) { + return d.label; + }).attr('selected', true); - 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); - }); - } - } + //clear the file input & disable the load button + loadStatus.text(files[0].name + ' loaded').style('color', 'green'); - 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; - }); - } + cat.controls.dataFileLoadButton.attr('disabled', true); + cat.controls.dataFileLoad.property('value', ''); + }; - 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)); - }); - } + fr.readAsText(files.item(0)); + }); + } - function initChartConfig(cat) { - var settingsHeading = cat.controls.settingsWrap - .append('h3') - .html('3. Customize the Chart '); + function initChartConfig(cat) { + var settingsHeading = cat.controls.settingsWrap.append('h3').html('3. Customize the Chart '); - cat.controls.settingsWrap.append('span').text('Settings: '); + cat.controls.settingsWrap.append('span').text('Settings: '); - /* + /* ////////////////////////////////////// //initialize the config status icon ////////////////////////////////////// @@ -1304,496 +1096,387 @@ 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); - } - - 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); - } - - 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); - } - - 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(); - }); - }); - } - - /*------------------------------------------------------------------------------------------------\ + ////////////////////////////////////////////////////////////////////// + //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); + } + + 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); + } + + 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); + } + + 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: [] - }; - - 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; - }); - } - - 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); - } - - 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." - } - ]; - - var myStatus = statusOptions.filter(function(d) { - return d.key == statusVal; - })[0]; - - cat.controls.settingsStatus - .html(myStatus.symbol) - .style('color', myStatus.color) - .attr('title', myStatus.details); - } - - 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; - } - - 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'); - - cat.controls.settingsTypeForm - .property('checked', cat.current.settingsView == 'form') - .property('disabled', !cat.current.hasValidSchema); - - // Show/Hide sections - cat.controls.settingsInput.classed('hidden', cat.current.settingsView != 'text'); - cat.controls.settingsForm.classed('hidden', cat.current.settingsView != 'form'); - - //update the text or make the schema - cat.controls.settingsInput.node().value = JSON5.stringify(cat.current.config, null, 4); - - 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; - } - - // 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); - } - } - - 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); - } - } + var controls = { + init: init$1, + addEnterEventListener: addEnterEventListener + }; + + var defaultSettings = { + useServer: false, + rootURL: null, + dataURL: null, + dataFiles: [], + renderers: [] + }; + + 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; + }); + } + + 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); + } + + 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." + }]; + + var myStatus = statusOptions.filter(function (d) { + return d.key == statusVal; + })[0]; + + cat.controls.settingsStatus.html(myStatus.symbol).style('color', myStatus.color).attr('title', myStatus.details); + } + + 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; + } + + 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'); + + cat.controls.settingsTypeForm.property('checked', cat.current.settingsView == 'form').property('disabled', !cat.current.hasValidSchema); + + // Show/Hide sections + cat.controls.settingsInput.classed('hidden', cat.current.settingsView != 'text'); + cat.controls.settingsForm.classed('hidden', cat.current.settingsView != 'form'); + + //update the text or make the schema + cat.controls.settingsInput.node().value = JSON5.stringify(cat.current.config, null, 4); + + 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; + } + + // 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); + } + } + + 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); + } + } + + /*------------------------------------------------------------------------------------------------\ 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); - } - } - - 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.'); - }); - }); - } - - 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); - } - - /*------------------------------------------------------------------------------------------------\ + 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); + } + } + + 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.'); + }); + }); + } + + 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 - }; - - 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; -}); + var status = { + chartCreateStatus: chartCreateStatus, + chartInitStatus: chartInitStatus, + saveToServer: saveToServer, + loadStatus: loadStatus + }; + + 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; + +}))); diff --git a/package.json b/package.json index c533dc1..1da81d4 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,7 +12,7 @@ "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", diff --git a/src/cat/controls/initRendererSelect.js b/src/cat/controls/initRendererSelect.js index 3737446..7273ed2 100644 --- a/src/cat/controls/initRendererSelect.js +++ b/src/cat/controls/initRendererSelect.js @@ -1,4 +1,5 @@ import updateRenderer from './initRendererSelect/updateRenderer'; +import getVersions from './initRendererSelect/getVersions'; export function initRendererSelect(cat) { cat.controls.rendererWrap.append('h3').text('1. Choose a Charting Library'); @@ -17,9 +18,14 @@ export function initRendererSelect(cat) { }); 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 = cat.controls.rendererWrap.append('select'); + getVersions( + cat.controls.versionSelect, + cat.current.url + ); + //cat.controls.versionSelect.node().value = 'master'; cat.controls.versionSelect.on('input', function() { + console.log(this.value); cat.current.version = this.value; }); cat.controls.versionSelect.on('change', function() { diff --git a/src/cat/controls/initRendererSelect/getVersions.js b/src/cat/controls/initRendererSelect/getVersions.js new file mode 100644 index 0000000..d1a3ff3 --- /dev/null +++ b/src/cat/controls/initRendererSelect/getVersions.js @@ -0,0 +1,22 @@ +export default function getVersions(select, repo = 'https://www.github.com/RhoInc/Webcharts') { + console.log(this); + console.log(select); + console.log(repo); + const api = repo.replace('www.github.com', 'api.github.com/repos'); + const branches = fetch(`${api}/branches`).then(response => response.json()); + const releases = fetch(`${api}/releases`).then(response => response.json()); + + Promise + .all([ + branches, + releases + ]) + .then(values => { + select + .selectAll('option') + .data(d3.merge(values)) + .enter() + .append('option') + .text(d => d.name); + }); +} From 15834d699e020f912dab34689d0fffdf90d830a0 Mon Sep 17 00:00:00 2001 From: samussiah Date: Tue, 21 May 2019 17:23:58 -0400 Subject: [PATCH 2/8] load branches and releases for renderers and webcharts --- build/cat.js | 3231 ++++++++++------- css/cat.css | 149 +- index.js | 46 +- package-lock.json | 8 +- package.json | 8 +- src/cat/controls/initRendererSelect.js | 26 +- .../initRendererSelect/getVersions.js | 35 +- .../controls/initSubmit/addControlsToggle.js | 67 +- 8 files changed, 2027 insertions(+), 1543 deletions(-) diff --git a/build/cat.js b/build/cat.js index 133ca0e..23f7671 100644 --- a/build/cat.js +++ b/build/cat.js @@ -1,1090 +1,1402 @@ -(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); - }); - } - ); - } +(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; + // Store setTimeout reference so promise-polyfill will be unaffected by + // other code modifying setTimeout (like sinon.useFakeTimers()) + var setTimeoutFunc = setTimeout; - function noop() {} + 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; + // Polyfill for Function.prototype.bind + function bind(fn, thisArg) { + return function() { + fn.apply(thisArg, arguments); + }; } - if (self._state === 0) { - self._deferreds.push(deferred); - return; + + /** + * @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); } - 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 handle(self, deferred) { + while (self._state === 3) { + self = self._value; + } + if (self._state === 0) { + self._deferreds.push(deferred); + return; } - } - self._state = 1; - self._value = newValue; - finale(self); - } catch (e) { - reject(self, e); + 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 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); + + 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); } - }); } - for (var i = 0, len = self._deferreds.length; i < len; i++) { - handle(self, self._deferreds[i]); + function reject(self, newValue) { + self._state = 2; + self._value = newValue; + finale(self); } - 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); + + 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]); } - ); - } catch (ex) { - if (done) return; - done = true; - reject(self, ex); + self._deferreds = null; } - } - Promise$1.prototype['catch'] = function(onRejected) { - return this.then(null, onRejected); - }; + /** + * @constructor + */ + function Handler(onFulfilled, onRejected, promise) { + this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; + this.onRejected = typeof onRejected === 'function' ? onRejected : null; + this.promise = promise; + } - Promise$1.prototype.then = function(onFulfilled, onRejected) { - // @ts-ignore - var prom = new this.constructor(noop); + /** + * 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); + } + } - handle(this, new Handler(onFulfilled, onRejected, prom)); - return prom; - }; + Promise$1.prototype['catch'] = function(onRejected) { + return this.then(null, onRejected); + }; - Promise$1.prototype['finally'] = finallyConstructor; + Promise$1.prototype.then = function(onFulfilled, onRejected) { + // @ts-ignore + var prom = new this.constructor(noop); - 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; + handle(this, new Handler(onFulfilled, onRejected, prom)); + return prom; + }; - 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; + 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); + } } - } - args[i] = val; - if (--remaining === 0) { - resolve(args); - } - } catch (ex) { - reject(ex); + + for (var i = 0; i < args.length; i++) { + res(i, args[i]); + } + }); + }; + + Promise$1.resolve = function(value) { + if (value && typeof value === 'object' && value.constructor === Promise$1) { + return value; } - } - for (var i = 0; i < args.length; i++) { - res(i, args[i]); - } - }); - }; + return new Promise$1(function(resolve) { + resolve(value); + }); + }; - Promise$1.resolve = function(value) { - if (value && typeof value === 'object' && value.constructor === Promise$1) { - return value; - } + Promise$1.reject = function(value) { + return new Promise$1(function(resolve, reject) { + reject(value); + }); + }; - 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); + 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); + } + }); }; - Promise$1._unhandledRejectionFn = function _unhandledRejectionFn(err) { - if (typeof console !== 'undefined' && console) { - console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console + // 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 + } + }; + + /** @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; } - }; - - /** @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 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 + }); } - if (typeof window !== 'undefined') { - return window; + + 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 (typeof global !== 'undefined') { - return global; + + 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; + } + }); } - 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); + function init() { + //layout the cat + this.wrap = d3 + .select(this.element) + .append('div') + .attr('class', 'cat-wrap'); + this.layout(this); - for (var index = 1; index < arguments.length; index++) { - var nextSource = arguments[index]; + //initialize the settings + this.setDefaults(this); - 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]; - } - } - } - } + //add others here! - 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'); - } + //create the controls + this.controls.init(this); + } - var o = Object(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); + } - // 2. Let len be ? ToLength(? Get(O, 'length')). - var len = o.length >>> 0; + function addControlsToggle() { + var _this = this; - // 3. If IsCallable(predicate) is false, throw a TypeError exception. - if (typeof predicate !== 'function') { - throw new TypeError('predicate must be a function'); - } + 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; + + //Minimize controls. + this.controls.minimize = this.wrap + .append('div') + .classed('cat-button cat-button--minimize hidden', true) + .attr('title', 'Hide controls') + .text('<<'); + this.controls.minimize.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.minimize.classed('hidden', true); + _this.controls.maximize.classed('hidden', false); + }); - // 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++; - } + //Maximize controls. + this.controls.maximize = this.wrap + .append('div') + .classed('cat-button cat-button--maximize hidden', true) + .attr('title', 'Show controls') + .text('>>'); + this.controls.maximize.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.controls.minimize.classed('hidden', false); + _this.controls.maximize.classed('hidden', true); + }); + } - // 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'); + 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 o = Object(this); + 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; + } + } catch (err) { + _d = true; + _e = err; + } finally { + try { + if (!_n && _i['return']) _i['return'](); + } finally { + if (_d) throw _e; + } + } - // 2. Let len be ? ToLength(? Get(O, "length")). - var len = o.length >>> 0; + return _arr; + } - // 3. If IsCallable(predicate) is false, throw a TypeError exception. - if (typeof predicate !== 'function') { - throw new TypeError('predicate must be a function'); - } + 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'); + } + }; + })(); + + // 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); + } + }, - // 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++; - } + addEvent: function addEvent(el, eventName, eventFunc) { + if ((typeof el === 'undefined' ? 'undefined' : _typeof(el)) != 'object') { + return false; + } - // 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; - } - }; - - 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; - } - - 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 = ""; - } - - /* 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(); - } - - 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); - } + if (el.addEventListener) { + el.addEventListener(eventName, eventFunc, false); + return true; + } - cat.current.htmlExport = createChartExport(cat); // save the source code before init + if (el.attachEvent) { + el.attachEvent('on' + eventName, eventFunc); + return true; + } - try { - myChart.init(data); - } catch (err) { - cat.status.chartInitStatus(cat.statusDiv, false, err); - } finally { - cat.status.chartInitStatus(cat.statusDiv, true, null, cat.current.htmlExport); + return false; + }, - // save to server button - if (cat.config.useServer) { - cat.status.saveToServer(cat); - } - showEnv(cat); + // add script to dom + require: function require(url, args) { + var me = this; + args = args || {}; - //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); - }); - } - } - - 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); - } - }); - } - - function loadLibrary(cat) { - var version = cat.controls.libraryVersion.node().value; - var library = 'webcharts'; //hardcode to webcharts for now - could generalize later - - // --- load css --- // - var cssPath = version !== 'master' ? cat.config.rootURL + '/Webcharts@' + version + '/css/webcharts.css' : cat.config.rootURL + '/Webcharts/css/webcharts.css'; - - 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); - } - - // --- load js --- // - var rendererPath = version !== 'master' ? cat.config.rootURL + '/' + library + '@' + version + '/build/webcharts.js' : cat.config.rootURL + '/Webcharts/build/webcharts.js'; - - 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); - }, - failure: function failure() { - cat.status.loadStatus(cat.statusDiv, false, rendererPath, library, version); - } - }); - } else { - cat.status.loadStatus(cat.statusDiv, true, rendererPath, library, version); - loadRenderer(cat); - } - } - - 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); - }); - } - - function initSubmit(cat) { - addControlsToggle.call(cat); - addSubmitButton.call(cat); - } - - 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 getVersions(select) { - var repo = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'https://www.github.com/RhoInc/Webcharts'; - - console.log(this); - console.log(select); - console.log(repo); - var api = repo.replace('www.github.com', 'api.github.com/repos'); - var branches = fetch(api + '/branches').then(function (response) { - return response.json(); - }); - var releases = fetch(api + '/releases').then(function (response) { - return response.json(); - }); - - Promise.all([branches, releases]).then(function (values) { - select.selectAll('option').data(d3.merge(values)).enter().append('option').text(function (d) { - return d.name; - }); - }); - } - - 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('select'); - getVersions(cat.controls.versionSelect, cat.current.url); - //cat.controls.versionSelect.node().value = 'master'; - cat.controls.versionSelect.on('input', 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').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; - }); - } - - 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); + var scriptTag = document.createElement('script'); + var headTag = document.getElementsByTagName('head')[0]; + if (!headTag) { + return false; + } - //add it to the select dropdown - cat.controls.dataFileSelect.append('option').datum(dataObject).text(function (d) { - return d.label; - }).attr('selected', true); + 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; + } + }; - //clear the file input & disable the load button - loadStatus.text(files[0].name + ' loaded').style('color', 'green'); + 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; + } - cat.controls.dataFileLoadButton.attr('disabled', true); - cat.controls.dataFileLoad.property('value', ''); - }; + 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; + } - fr.readAsText(files.item(0)); - }); - } + 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 = ""; + } + + /* 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; + }); - function initChartConfig(cat) { - var settingsHeading = cat.controls.settingsWrap.append('h3').html('3. Customize the Chart '); + 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(); + } + + 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); + }); + } + } + + 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); + } + }); + } - cat.controls.settingsWrap.append('span').text('Settings: '); + function loadLibrary(cat) { + var version = cat.controls.libraryVersion.node().value; + var library = 'webcharts'; //hardcode to webcharts for now - could generalize later - /* + // --- load css --- // + var cssPath = + version !== 'master' + ? cat.config.rootURL + '/Webcharts@' + version + '/css/webcharts.css' + : cat.config.rootURL + '/Webcharts/css/webcharts.css'; + + 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); + } + + // --- load js --- // + var rendererPath = + version !== 'master' + ? cat.config.rootURL + '/' + library + '@' + version + '/build/webcharts.js' + : cat.config.rootURL + '/Webcharts/build/webcharts.js'; + + 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); + }, + failure: function failure() { + cat.status.loadStatus(cat.statusDiv, false, rendererPath, library, version); + } + }); + } else { + cat.status.loadStatus(cat.statusDiv, true, rendererPath, library, version); + loadRenderer(cat); + } + } + + 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); + }); + } + + function initSubmit(cat) { + addControlsToggle.call(cat); + addSubmitButton.call(cat); + } + + 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 getVersions(select) { + var repo = + arguments.length > 1 && arguments[1] !== undefined + ? arguments[1] + : 'https://api.github.com/repos/RhoInc/Webcharts'; + + var branches = fetch(repo + '/branches').then(function(response) { + return response.json(); + }); + var releases = fetch(repo + '/releases').then(function(response) { + return response.json(); + }); + + 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; + }); + select.selectAll('option').remove(); + select + .selectAll('option') + .data(d3.merge(values)) + .enter() + .append('option') + .text(function(d) { + return d.name; + }) + .property('selected', function(d) { + return d.name === 'master'; + }); + }) + .catch(function(err) { + console.log(err); + }); + } + + function initRendererSelect(cat) { + cat.controls.rendererWrap.append('h3').text('1. Choose a Charting Library'); + cat.controls.rendererWrap.append('span').text('Library: '); + + //renderers + 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); + getVersions(cat.controls.versionSelect, cat.current.api_url); + }); + cat.controls.rendererWrap.append('br'); + + //renderer version + cat.controls.rendererWrap.append('span').text('Version: '); + cat.controls.versionSelect = cat.controls.rendererWrap.append('select'); + getVersions(cat.controls.versionSelect, cat.current.api_url); + //cat.controls.versionSelect.node().value = 'master'; + cat.controls.versionSelect.on('input', 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') + .on('click', function() { + d3.select(this).remove(); + cat.controls.rendererWrap.selectAll('*').classed('hidden', false); + }); + + //name of method that creates 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); + + //name of method that initializes chart + 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 version + cat.controls.rendererWrap + .append('span') + .text('Webcharts Version: ') + .classed('hidden', true); + cat.controls.libraryVersion = cat.controls.rendererWrap + .append('select') + .classed('hidden', true); + getVersions(cat.controls.libraryVersion); + //cat.controls.libraryVersion.node().value = 'master'; + cat.controls.rendererWrap.append('br').classed('hidden', true); + + //schema + 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; + }); + } + + 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)); + }); + } + + 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 ////////////////////////////////////// @@ -1096,387 +1408,496 @@ 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); - } - - 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); - } - - 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); - } - - 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(); - }); - }); - } - - /*------------------------------------------------------------------------------------------------\ + ////////////////////////////////////////////////////////////////////// + //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); + } + + 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); + } + + 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); + } + + 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: [] - }; - - 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; - }); - } - - 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); - } - - 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." - }]; - - var myStatus = statusOptions.filter(function (d) { - return d.key == statusVal; - })[0]; - - cat.controls.settingsStatus.html(myStatus.symbol).style('color', myStatus.color).attr('title', myStatus.details); - } - - 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; - } - - 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'); - - cat.controls.settingsTypeForm.property('checked', cat.current.settingsView == 'form').property('disabled', !cat.current.hasValidSchema); - - // Show/Hide sections - cat.controls.settingsInput.classed('hidden', cat.current.settingsView != 'text'); - cat.controls.settingsForm.classed('hidden', cat.current.settingsView != 'form'); - - //update the text or make the schema - cat.controls.settingsInput.node().value = JSON5.stringify(cat.current.config, null, 4); - - 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; - } - - // 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); - } + var controls = { + init: init$1, + addEnterEventListener: addEnterEventListener + }; - 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); - } - } - - 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); - } - } - - /*------------------------------------------------------------------------------------------------\ + var defaultSettings = { + useServer: false, + rootURL: null, + dataURL: null, + dataFiles: [], + renderers: [] + }; + + 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; + }); + } + + 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); + } + + 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." + } + ]; + + var myStatus = statusOptions.filter(function(d) { + return d.key == statusVal; + })[0]; + + cat.controls.settingsStatus + .html(myStatus.symbol) + .style('color', myStatus.color) + .attr('title', myStatus.details); + } + + 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; + } + + 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'); + + cat.controls.settingsTypeForm + .property('checked', cat.current.settingsView == 'form') + .property('disabled', !cat.current.hasValidSchema); + + // Show/Hide sections + cat.controls.settingsInput.classed('hidden', cat.current.settingsView != 'text'); + cat.controls.settingsForm.classed('hidden', cat.current.settingsView != 'form'); + + //update the text or make the schema + cat.controls.settingsInput.node().value = JSON5.stringify(cat.current.config, null, 4); + + 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; + } + + // 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); + } + } + + 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); + } + } + + /*------------------------------------------------------------------------------------------------\ 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); - } - } - - 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.'); - }); - }); - } - - 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); - } - - /*------------------------------------------------------------------------------------------------\ + 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); + } + } + + 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.'); + }); + }); + } + + 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 - }; - - 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; - -}))); + var status = { + chartCreateStatus: chartCreateStatus, + chartInitStatus: chartInitStatus, + saveToServer: saveToServer, + loadStatus: loadStatus + }; + + 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; +}); diff --git a/css/cat.css b/css/cat.css index 687ef60..8dd6ddf 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 { @@ -48,44 +55,48 @@ } .cat-wrap .cat-button { position: absolute; - left: 0; - top: 5px; + top: 3px; + left: 5px; 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.js b/index.js index 573c7cd..017ef39 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,9 @@ -var myCatConfig = { +const myCatConfig = { useServer: false, rootURL: 'https://cdn.jsdelivr.net/gh/RhoInc', dataURL: 'https://raw.githubusercontent.com/RhoInc/data-library/master/data/', + repoURL: 'https://cdn.jsdelivr.net/gh/RhoInc/graphics/data', + repoURL: '../graphics/data', renderers: [ { name: 'web-codebook', @@ -82,13 +84,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 +137,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..b7efdc6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cat", - "version": "0.9.0", + "version": "0.10.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1590,9 +1590,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", diff --git a/package.json b/package.json index 1da81d4..bd0d577 100644 --- a/package.json +++ b/package.json @@ -17,12 +17,12 @@ "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" + "jquery": "^3.4.1", + "json5": "^0.5.1", + "webcharts": "~1" }, "devDependencies": { "babel-plugin-external-helpers": "^6.22.0", diff --git a/src/cat/controls/initRendererSelect.js b/src/cat/controls/initRendererSelect.js index 7273ed2..be0d95f 100644 --- a/src/cat/controls/initRendererSelect.js +++ b/src/cat/controls/initRendererSelect.js @@ -5,6 +5,7 @@ export function initRendererSelect(cat) { cat.controls.rendererWrap.append('h3').text('1. Choose a Charting Library'); cat.controls.rendererWrap.append('span').text('Library: '); + //renderers cat.controls.rendererSelect = cat.controls.rendererWrap.append('select'); cat.controls.rendererSelect .selectAll('option') @@ -12,17 +13,16 @@ export function initRendererSelect(cat) { .enter() .append('option') .text(d => d.name); - cat.controls.rendererSelect.on('change', function() { updateRenderer.call(cat, this); + getVersions(cat.controls.versionSelect, cat.current.api_url); }); cat.controls.rendererWrap.append('br'); + + //renderer version cat.controls.rendererWrap.append('span').text('Version: '); cat.controls.versionSelect = cat.controls.rendererWrap.append('select'); - getVersions( - cat.controls.versionSelect, - cat.current.url - ); + getVersions(cat.controls.versionSelect, cat.current.api_url); //cat.controls.versionSelect.node().value = 'master'; cat.controls.versionSelect.on('input', function() { console.log(this.value); @@ -32,7 +32,6 @@ export function initRendererSelect(cat) { cat.settings.set(cat); }); cat.controls.rendererWrap.append('br'); - cat.controls.rendererWrap .append('a') .text('More Options') @@ -44,7 +43,7 @@ export function initRendererSelect(cat) { cat.controls.rendererWrap.selectAll('*').classed('hidden', false); }); - //specify the code to create the chart + //name of method that creates the chart cat.controls.rendererWrap .append('span') .text(' Init: ') @@ -55,18 +54,25 @@ export function initRendererSelect(cat) { .append('span') .text('.') .classed('hidden', true); + + //name of method that initializes chart 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 + + //Webcharts version 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.libraryVersion = cat.controls.rendererWrap + .append('select') + .classed('hidden', true); + getVersions(cat.controls.libraryVersion); + //cat.controls.libraryVersion.node().value = 'master'; cat.controls.rendererWrap.append('br').classed('hidden', true); + //schema cat.controls.rendererWrap .append('span') .text('Schema: ') diff --git a/src/cat/controls/initRendererSelect/getVersions.js b/src/cat/controls/initRendererSelect/getVersions.js index d1a3ff3..6be49ff 100644 --- a/src/cat/controls/initRendererSelect/getVersions.js +++ b/src/cat/controls/initRendererSelect/getVersions.js @@ -1,22 +1,27 @@ -export default function getVersions(select, repo = 'https://www.github.com/RhoInc/Webcharts') { - console.log(this); - console.log(select); - console.log(repo); - const api = repo.replace('www.github.com', 'api.github.com/repos'); - const branches = fetch(`${api}/branches`).then(response => response.json()); - const releases = fetch(`${api}/releases`).then(response => response.json()); +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 - ]) + 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() + .data(d3.merge(values)) + .enter() .append('option') - .text(d => d.name); + .text(d => d.name) + .property('selected', d => d.name === 'master'); + }) + .catch(err => { + console.log(err); }); } diff --git a/src/cat/controls/initSubmit/addControlsToggle.js b/src/cat/controls/initSubmit/addControlsToggle.js index 205c369..bab4a44 100644 --- a/src/cat/controls/initSubmit/addControlsToggle.js +++ b/src/cat/controls/initSubmit/addControlsToggle.js @@ -1,35 +1,46 @@ export default function addControlsToggle() { - const cat = this; + 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; - this.controls.minimize = this.controls.submitWrap + //Minimize controls. + this.controls.minimize = this.wrap .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(); - }); + .text('<<'); + this.controls.minimize.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.minimize.classed('hidden', true); + this.controls.maximize.classed('hidden', false); + }); + + //Maximize controls. + this.controls.maximize = this.wrap + .append('div') + .classed('cat-button cat-button--maximize hidden', true) + .attr('title', 'Show controls') + .text('>>'); + this.controls.maximize.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.controls.minimize.classed('hidden', false); + this.controls.maximize.classed('hidden', true); + }); } From 8521d90722b8e1bc28b5c6edcca992d85c044c63 Mon Sep 17 00:00:00 2001 From: samussiah Date: Wed, 22 May 2019 10:32:26 -0400 Subject: [PATCH 3/8] need to call destroy method on previous chart --- build/cat.js | 3295 ++++++++--------- .../controls/initSubmit/addSubmitButton.js | 5 + src/cat/renderChart.js | 9 +- 3 files changed, 1485 insertions(+), 1824 deletions(-) diff --git a/build/cat.js b/build/cat.js index 23f7671..9751788 100644 --- a/build/cat.js +++ b/build/cat.js @@ -1,1402 +1,1164 @@ -(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); - }); - } - ); - } +(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; + // Store setTimeout reference so promise-polyfill will be unaffected by + // other code modifying setTimeout (like sinon.useFakeTimers()) + var setTimeoutFunc = setTimeout; - function noop() {} + function noop() {} - // Polyfill for Function.prototype.bind - function bind(fn, thisArg) { - return function() { - fn.apply(thisArg, arguments); - }; + // 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; } - - /** - * @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); + if (self._state === 0) { + self._deferreds.push(deferred); + return; } - - 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 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._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); - }); + } + self._state = 1; + self._value = newValue; + finale(self); + } catch (e) { + reject(self, e); } - - 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); } + }); } - function reject(self, newValue) { - self._state = 2; - self._value = newValue; - finale(self); + for (var i = 0, len = self._deferreds.length; i < len; i++) { + handle(self, self._deferreds[i]); } - - 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); } - self._deferreds = null; + ); + } catch (ex) { + if (done) return; + done = true; + reject(self, ex); } + } - /** - * @constructor - */ - function Handler(onFulfilled, onRejected, promise) { - this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; - this.onRejected = typeof onRejected === 'function' ? onRejected : null; - this.promise = promise; - } + Promise$1.prototype['catch'] = function(onRejected) { + return this.then(null, onRejected); + }; - /** - * 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.then = function(onFulfilled, onRejected) { + // @ts-ignore + var prom = new this.constructor(noop); - Promise$1.prototype['catch'] = function(onRejected) { - return this.then(null, onRejected); - }; + handle(this, new Handler(onFulfilled, onRejected, prom)); + return prom; + }; - Promise$1.prototype.then = function(onFulfilled, onRejected) { - // @ts-ignore - var prom = new this.constructor(noop); + Promise$1.prototype['finally'] = finallyConstructor; - handle(this, new Handler(onFulfilled, onRejected, prom)); - return prom; - }; + 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; - 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 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; } - }); - }; - - Promise$1.resolve = function(value) { - if (value && typeof value === 'object' && value.constructor === Promise$1) { - return value; + } + args[i] = val; + if (--remaining === 0) { + resolve(args); + } + } catch (ex) { + reject(ex); } + } - return new Promise$1(function(resolve) { - resolve(value); - }); - }; + for (var i = 0; i < args.length; i++) { + res(i, args[i]); + } + }); + }; - 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); - } - }); - }; + Promise$1.resolve = function(value) { + if (value && typeof value === 'object' && value.constructor === Promise$1) { + return value; + } - // 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 - } + 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); }; - /** @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; + Promise$1._unhandledRejectionFn = function _unhandledRejectionFn(err) { + if (typeof console !== 'undefined' && console) { + console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console } - - 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 - }); + }; + + /** @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 (!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 (typeof window !== 'undefined') { + return window; } - - 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; - } - }); + if (typeof global !== 'undefined') { + return global; } - - 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 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; - - //Minimize controls. - this.controls.minimize = this.wrap - .append('div') - .classed('cat-button cat-button--minimize hidden', true) - .attr('title', 'Hide controls') - .text('<<'); - this.controls.minimize.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.minimize.classed('hidden', true); - _this.controls.maximize.classed('hidden', false); - }); - - //Maximize controls. - this.controls.maximize = this.wrap - .append('div') - .classed('cat-button cat-button--maximize hidden', true) - .attr('title', 'Show controls') - .text('>>'); - this.controls.maximize.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.controls.minimize.classed('hidden', false); - _this.controls.maximize.classed('hidden', true); - }); - } - - var _typeof = - typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol' - ? function(obj) { - return typeof obj; + 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(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; - } - } catch (err) { - _d = true; - _e = err; - } finally { - try { - if (!_n && _i['return']) _i['return'](); - } finally { - if (_d) throw _e; - } - } - - return _arr; - } - - 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'); - } - }; - })(); - - // 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 to = Object(target); - 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; - } - }; + for (var index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; - 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; - } - - 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 = ""; - } + 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 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; - } + 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'); + } - 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; - }); + var o = Object(this); - 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; - }); + // 2. Let len be ? ToLength(? Get(O, 'length')). + var len = o.length >>> 0; - 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(); - } + // 3. If IsCallable(predicate) is false, throw a TypeError exception. + if (typeof predicate !== 'function') { + throw new TypeError('predicate must be a function'); + } - 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; - } - } - } + // 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++; + } - 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); - }); - } - } + // 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'); + } - 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 o = Object(this); - 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); - } - }); - } + // 2. Let len be ? ToLength(? Get(O, "length")). + var len = o.length >>> 0; - function loadLibrary(cat) { - var version = cat.controls.libraryVersion.node().value; - var library = 'webcharts'; //hardcode to webcharts for now - could generalize later + // 3. If IsCallable(predicate) is false, throw a TypeError exception. + if (typeof predicate !== 'function') { + throw new TypeError('predicate must be a function'); + } - // --- load css --- // - var cssPath = - version !== 'master' - ? cat.config.rootURL + '/Webcharts@' + version + '/css/webcharts.css' - : cat.config.rootURL + '/Webcharts/css/webcharts.css'; + // 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++; + } - 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); + // 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 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; + + //Minimize controls. + this.controls.minimize = this.wrap.append('div').classed('cat-button cat-button--minimize hidden', true).attr('title', 'Hide controls').text('<<'); + this.controls.minimize.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.minimize.classed('hidden', true); + _this.controls.maximize.classed('hidden', false); + }); + + //Maximize controls. + this.controls.maximize = this.wrap.append('div').classed('cat-button cat-button--maximize hidden', true).attr('title', 'Show controls').text('>>'); + this.controls.maximize.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.controls.minimize.classed('hidden', false); + _this.controls.maximize.classed('hidden', true); + }); + } + + 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; } - - // --- load js --- // - var rendererPath = - version !== 'master' - ? cat.config.rootURL + '/' + library + '@' + version + '/build/webcharts.js' - : cat.config.rootURL + '/Webcharts/build/webcharts.js'; - - 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); - }, - failure: function failure() { - cat.status.loadStatus(cat.statusDiv, false, rendererPath, library, version); - } - }); - } else { - cat.status.loadStatus(cat.statusDiv, true, rendererPath, library, version); - loadRenderer(cat); + } catch (err) { + _d = true; + _e = err; + } finally { + try { + if (!_n && _i["return"]) _i["return"](); + } finally { + if (_d) throw _e; } - } - - 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); - }); - } + } - function initSubmit(cat) { - addControlsToggle.call(cat); - addSubmitButton.call(cat); + return _arr; } - 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); - } + 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"); + } + }; + }(); + + // 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; + } + }; + + 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; + } + + 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 = ""; + } + + /* 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(); + } + + 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) { + 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 { + cat.current.instance = window[cat.current.main]('.cat-chart .chart', cat.current.config); + cat.status.chartCreateStatus(cat.statusDiv, cat.current.main); + } - function getVersions(select) { - var repo = - arguments.length > 1 && arguments[1] !== undefined - ? arguments[1] - : 'https://api.github.com/repos/RhoInc/Webcharts'; + cat.current.htmlExport = createChartExport(cat); // save the source code before init - var branches = fetch(repo + '/branches').then(function(response) { - return response.json(); - }); - var releases = fetch(repo + '/releases').then(function(response) { - return response.json(); - }); + try { + cat.current.instance.init(data); + } catch (err) { + cat.status.chartInitStatus(cat.statusDiv, false, err); + } finally { + cat.status.chartInitStatus(cat.statusDiv, true, null, cat.current.htmlExport); - 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; - }); - select.selectAll('option').remove(); - select - .selectAll('option') - .data(d3.merge(values)) - .enter() - .append('option') - .text(function(d) { - return d.name; - }) - .property('selected', function(d) { - return d.name === 'master'; - }); - }) - .catch(function(err) { - console.log(err); - }); - } + // save to server button + if (cat.config.useServer) { + cat.status.saveToServer(cat); + } + showEnv(cat); - function initRendererSelect(cat) { - cat.controls.rendererWrap.append('h3').text('1. Choose a Charting Library'); - cat.controls.rendererWrap.append('span').text('Library: '); - - //renderers - 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); - getVersions(cat.controls.versionSelect, cat.current.api_url); - }); - cat.controls.rendererWrap.append('br'); - - //renderer version - cat.controls.rendererWrap.append('span').text('Version: '); - cat.controls.versionSelect = cat.controls.rendererWrap.append('select'); - getVersions(cat.controls.versionSelect, cat.current.api_url); - //cat.controls.versionSelect.node().value = 'master'; - cat.controls.versionSelect.on('input', 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') - .on('click', function() { - d3.select(this).remove(); - cat.controls.rendererWrap.selectAll('*').classed('hidden', false); - }); - - //name of method that creates 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); - - //name of method that initializes chart - 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 version - cat.controls.rendererWrap - .append('span') - .text('Webcharts Version: ') - .classed('hidden', true); - cat.controls.libraryVersion = cat.controls.rendererWrap - .append('select') - .classed('hidden', true); - getVersions(cat.controls.libraryVersion); - //cat.controls.libraryVersion.node().value = 'master'; - cat.controls.rendererWrap.append('br').classed('hidden', true); - - //schema - 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); - } + //don't print any new statuses until a new chart is rendered + cat.printStatus = false; + } + console.log(cat.current.instance); + } + } + + if (dataObject.user_loaded) { + dataObject.json = d3.csv.parse(dataObject.csv_raw); + render(false, dataObject.json); + console.log(cat.current.instance); + } else { + var dataFilePath = dataObject.path + dataFile; + d3.csv(dataFilePath, function (error, data) { + render(error, data); + console.log(cat.current.instance); + }); + } + } + + 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); + } + }); + } + + function loadLibrary(cat) { + var version = cat.controls.libraryVersion.node().value; + var library = 'webcharts'; //hardcode to webcharts for now - could generalize later + + // --- load css --- // + var cssPath = version !== 'master' ? cat.config.rootURL + '/Webcharts@' + version + '/css/webcharts.css' : cat.config.rootURL + '/Webcharts/css/webcharts.css'; + + 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); + } + + // --- load js --- // + var rendererPath = version !== 'master' ? cat.config.rootURL + '/' + library + '@' + version + '/build/webcharts.js' : cat.config.rootURL + '/Webcharts/build/webcharts.js'; + + 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); + }, + failure: function failure() { + cat.status.loadStatus(cat.statusDiv, false, rendererPath, library, version); + } + }); + } else { + cat.status.loadStatus(cat.statusDiv, true, rendererPath, library, version); + loadRenderer(cat); + } + } + + function addSubmitButton() { + var _this = this; + + this.controls.submitButton = this.controls.submitWrap.append('button').attr('class', 'submit').text('Render Chart').on('click', function () { + _this.previous = _this.current; + _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(); + + if (_this.previous) { + console.log(_this.previous); + _this.previous.instance.destroy(); + } + _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); + }); + } + + function initSubmit(cat) { + addControlsToggle.call(cat); + addSubmitButton.call(cat); + } + + 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 getVersions(select) { + var repo = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'https://api.github.com/repos/RhoInc/Webcharts'; + + var branches = fetch(repo + '/branches').then(function (response) { + return response.json(); + }); + var releases = fetch(repo + '/releases').then(function (response) { + return response.json(); + }); + + 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; + }); + select.selectAll('option').remove(); + select.selectAll('option').data(d3.merge(values)).enter().append('option').text(function (d) { + return d.name; + }).property('selected', function (d) { + return d.name === 'master'; + }); + }).catch(function (err) { + console.log(err); + }); + } + + function initRendererSelect(cat) { + cat.controls.rendererWrap.append('h3').text('1. Choose a Charting Library'); + cat.controls.rendererWrap.append('span').text('Library: '); + + //renderers + 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); + getVersions(cat.controls.versionSelect, cat.current.api_url); + }); + cat.controls.rendererWrap.append('br'); + + //renderer version + cat.controls.rendererWrap.append('span').text('Version: '); + cat.controls.versionSelect = cat.controls.rendererWrap.append('select'); + getVersions(cat.controls.versionSelect, cat.current.api_url); + //cat.controls.versionSelect.node().value = 'master'; + cat.controls.versionSelect.on('input', 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').on('click', function () { + d3.select(this).remove(); + cat.controls.rendererWrap.selectAll('*').classed('hidden', false); + }); + + //name of method that creates 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); + + //name of method that initializes chart + 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 version + cat.controls.rendererWrap.append('span').text('Webcharts Version: ').classed('hidden', true); + cat.controls.libraryVersion = cat.controls.rendererWrap.append('select').classed('hidden', true); + getVersions(cat.controls.libraryVersion); + //cat.controls.libraryVersion.node().value = 'master'; + cat.controls.rendererWrap.append('br').classed('hidden', true); + + //schema + 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; + }); + } + + 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); - function showDataPreview(cat) { - cat.dataWrap.classed('hidden', false); - cat.chartWrap.classed('hidden', true); - cat.dataWrap.selectAll('*').remove(); + //add it to the select dropdown + cat.controls.dataFileSelect.append('option').datum(dataObject).text(function (d) { + return d.label; + }).attr('selected', true); - if (cat.dataPreview) { - cat.dataPreview.destroy(); - } + //clear the file input & disable the load button + loadStatus.text(files[0].name + ' loaded').style('color', 'green'); - 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); - }); - } - } + cat.controls.dataFileLoadButton.attr('disabled', true); + cat.controls.dataFileLoad.property('value', ''); + }; - 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; - }); - } + fr.readAsText(files.item(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)); - }); - } - - function initChartConfig(cat) { - var settingsHeading = cat.controls.settingsWrap - .append('h3') - .html('3. Customize the Chart '); + function initChartConfig(cat) { + var settingsHeading = cat.controls.settingsWrap.append('h3').html('3. Customize the Chart '); - cat.controls.settingsWrap.append('span').text('Settings: '); + cat.controls.settingsWrap.append('span').text('Settings: '); - /* + /* ////////////////////////////////////// //initialize the config status icon ////////////////////////////////////// @@ -1408,496 +1170,387 @@ 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); - } - - 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); - } - - 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); - } - - 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(); - }); - }); - } - - /*------------------------------------------------------------------------------------------------\ + ////////////////////////////////////////////////////////////////////// + //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); + } + + 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); + } + + 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); + } + + 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: [] - }; - - 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; - }); - } - - 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); - } - - 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." - } - ]; - - var myStatus = statusOptions.filter(function(d) { - return d.key == statusVal; - })[0]; - - cat.controls.settingsStatus - .html(myStatus.symbol) - .style('color', myStatus.color) - .attr('title', myStatus.details); - } - - 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; - } - - 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'); - - cat.controls.settingsTypeForm - .property('checked', cat.current.settingsView == 'form') - .property('disabled', !cat.current.hasValidSchema); - - // Show/Hide sections - cat.controls.settingsInput.classed('hidden', cat.current.settingsView != 'text'); - cat.controls.settingsForm.classed('hidden', cat.current.settingsView != 'form'); - - //update the text or make the schema - cat.controls.settingsInput.node().value = JSON5.stringify(cat.current.config, null, 4); - - 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; - } - - // 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); - } - } - - 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); - } - } + var controls = { + init: init$1, + addEnterEventListener: addEnterEventListener + }; + + var defaultSettings = { + useServer: false, + rootURL: null, + dataURL: null, + dataFiles: [], + renderers: [] + }; + + 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; + }); + } + + 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); + } + + 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." + }]; + + var myStatus = statusOptions.filter(function (d) { + return d.key == statusVal; + })[0]; + + cat.controls.settingsStatus.html(myStatus.symbol).style('color', myStatus.color).attr('title', myStatus.details); + } + + 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; + } + + 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'); + + cat.controls.settingsTypeForm.property('checked', cat.current.settingsView == 'form').property('disabled', !cat.current.hasValidSchema); + + // Show/Hide sections + cat.controls.settingsInput.classed('hidden', cat.current.settingsView != 'text'); + cat.controls.settingsForm.classed('hidden', cat.current.settingsView != 'form'); + + //update the text or make the schema + cat.controls.settingsInput.node().value = JSON5.stringify(cat.current.config, null, 4); + + 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; + } + + // 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); + } + } + + 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); + } + } + + /*------------------------------------------------------------------------------------------------\ 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); - } - } - - 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.'); - }); - }); - } - - 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); - } - - /*------------------------------------------------------------------------------------------------\ + 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); + } + } + + 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.'); + }); + }); + } + + 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 - }; - - 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; -}); + var status = { + chartCreateStatus: chartCreateStatus, + chartInitStatus: chartInitStatus, + saveToServer: saveToServer, + loadStatus: loadStatus + }; + + 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; + +}))); diff --git a/src/cat/controls/initSubmit/addSubmitButton.js b/src/cat/controls/initSubmit/addSubmitButton.js index 9dd805b..5b532b9 100644 --- a/src/cat/controls/initSubmit/addSubmitButton.js +++ b/src/cat/controls/initSubmit/addSubmitButton.js @@ -6,6 +6,7 @@ export default function addSubmitButton() { .attr('class', 'submit') .text('Render Chart') .on('click', () => { + this.previous = this.current; this.controls.minimize.classed('hidden', false); this.dataWrap.classed('hidden', true); this.chartWrap.classed('hidden', false); @@ -24,6 +25,10 @@ export default function addSubmitButton() { .property('disabled', true) .remove(); + if (this.previous) { + console.log(this.previous); + this.previous.instance.destroy(); + } this.chartWrap.selectAll('*').remove(); this.printStatus = true; this.statusDiv = this.chartWrap.append('div').attr('class', 'status'); diff --git a/src/cat/renderChart.js b/src/cat/renderChart.js index f1b0cf2..45e5353 100644 --- a/src/cat/renderChart.js +++ b/src/cat/renderChart.js @@ -17,20 +17,20 @@ 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 { @@ -45,16 +45,19 @@ export function renderChart(cat) { //don't print any new statuses until a new chart is rendered cat.printStatus = false; } + console.log(cat.current.instance); } } if (dataObject.user_loaded) { dataObject.json = d3.csv.parse(dataObject.csv_raw); render(false, dataObject.json); + console.log(cat.current.instance); } else { var dataFilePath = dataObject.path + dataFile; d3.csv(dataFilePath, function(error, data) { render(error, data); + console.log(cat.current.instance); }); } } From 4d9e0bb65b49c4ff4c6cf1765522bdceb50d7b96 Mon Sep 17 00:00:00 2001 From: Spencer Date: Fri, 24 May 2019 15:27:34 -0400 Subject: [PATCH 4/8] fix #7; fix #10; fix #65 --- build/cat.js | 3317 +++++++++-------- .../initRendererSelect/getVersions.js | 2 +- .../initRendererSelect/updateRenderer.js | 1 + .../controls/initSubmit/addSubmitButton.js | 22 +- src/cat/loadRenderer.js | 106 +- src/cat/renderChart.js | 9 +- src/util/scriptLoader.js | 2 +- 7 files changed, 1924 insertions(+), 1535 deletions(-) diff --git a/build/cat.js b/build/cat.js index 9751788..ed52b46 100644 --- a/build/cat.js +++ b/build/cat.js @@ -1,1164 +1,1424 @@ -(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); - }); - } - ); - } +(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; + // Store setTimeout reference so promise-polyfill will be unaffected by + // other code modifying setTimeout (like sinon.useFakeTimers()) + var setTimeoutFunc = setTimeout; - function noop() {} + 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; + // Polyfill for Function.prototype.bind + function bind(fn, thisArg) { + return function() { + fn.apply(thisArg, arguments); + }; } - if (self._state === 0) { - self._deferreds.push(deferred); - return; + + /** + * @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); } - 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 handle(self, deferred) { + while (self._state === 3) { + self = self._value; + } + if (self._state === 0) { + self._deferreds.push(deferred); + return; } - } - self._state = 1; - self._value = newValue; - finale(self); - } catch (e) { - reject(self, e); + 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 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); + + 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); } - }); } - for (var i = 0, len = self._deferreds.length; i < len; i++) { - handle(self, self._deferreds[i]); + function reject(self, newValue) { + self._state = 2; + self._value = newValue; + finale(self); } - 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); + + 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]); } - ); - } catch (ex) { - if (done) return; - done = true; - reject(self, ex); + self._deferreds = null; } - } - Promise$1.prototype['catch'] = function(onRejected) { - return this.then(null, onRejected); - }; + /** + * @constructor + */ + function Handler(onFulfilled, onRejected, promise) { + this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; + this.onRejected = typeof onRejected === 'function' ? onRejected : null; + this.promise = promise; + } - Promise$1.prototype.then = function(onFulfilled, onRejected) { - // @ts-ignore - var prom = new this.constructor(noop); + /** + * 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); + } + } - handle(this, new Handler(onFulfilled, onRejected, prom)); - return prom; - }; + Promise$1.prototype['catch'] = function(onRejected) { + return this.then(null, onRejected); + }; - Promise$1.prototype['finally'] = finallyConstructor; + Promise$1.prototype.then = function(onFulfilled, onRejected) { + // @ts-ignore + var prom = new this.constructor(noop); - 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; + handle(this, new Handler(onFulfilled, onRejected, prom)); + return prom; + }; - 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; + 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); + } } - } - args[i] = val; - if (--remaining === 0) { - resolve(args); - } - } catch (ex) { - reject(ex); + + for (var i = 0; i < args.length; i++) { + res(i, args[i]); + } + }); + }; + + Promise$1.resolve = function(value) { + if (value && typeof value === 'object' && value.constructor === Promise$1) { + return value; } - } - for (var i = 0; i < args.length; i++) { - res(i, args[i]); - } - }); - }; + return new Promise$1(function(resolve) { + resolve(value); + }); + }; - Promise$1.resolve = function(value) { - if (value && typeof value === 'object' && value.constructor === Promise$1) { - return value; - } + Promise$1.reject = function(value) { + return new Promise$1(function(resolve, reject) { + reject(value); + }); + }; - 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); + 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 + } }; - Promise$1._unhandledRejectionFn = function _unhandledRejectionFn(err) { - if (typeof console !== 'undefined' && console) { - console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console + /** @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; } - }; - - /** @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 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 + }); } - if (typeof window !== 'undefined') { - return window; + + 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 (typeof global !== 'undefined') { - return global; + + 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; + } + }); } - 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); + function init() { + //layout the cat + this.wrap = d3 + .select(this.element) + .append('div') + .attr('class', 'cat-wrap'); + this.layout(this); - for (var index = 1; index < arguments.length; index++) { - var nextSource = arguments[index]; + //initialize the settings + this.setDefaults(this); - 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]; - } - } - } - } + //add others here! - 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'); - } + //create the controls + this.controls.init(this); + } - var o = Object(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); + } - // 2. Let len be ? ToLength(? Get(O, 'length')). - var len = o.length >>> 0; + function addControlsToggle() { + var _this = this; - // 3. If IsCallable(predicate) is false, throw a TypeError exception. - if (typeof predicate !== 'function') { - throw new TypeError('predicate must be a function'); - } + 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; + + //Minimize controls. + this.controls.minimize = this.wrap + .append('div') + .classed('cat-button cat-button--minimize hidden', true) + .attr('title', 'Hide controls') + .text('<<'); + this.controls.minimize.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.minimize.classed('hidden', true); + _this.controls.maximize.classed('hidden', false); + }); - // 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++; - } + //Maximize controls. + this.controls.maximize = this.wrap + .append('div') + .classed('cat-button cat-button--maximize hidden', true) + .attr('title', 'Show controls') + .text('>>'); + this.controls.maximize.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.controls.minimize.classed('hidden', false); + _this.controls.maximize.classed('hidden', true); + }); + } - // 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'); + 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 o = Object(this); + 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; + } + } catch (err) { + _d = true; + _e = err; + } finally { + try { + if (!_n && _i['return']) _i['return'](); + } finally { + if (_d) throw _e; + } + } - // 2. Let len be ? ToLength(? Get(O, "length")). - var len = o.length >>> 0; + return _arr; + } - // 3. If IsCallable(predicate) is false, throw a TypeError exception. - if (typeof predicate !== 'function') { - throw new TypeError('predicate must be a function'); - } + 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'); + } + }; + })(); + + // 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); + } + }, - // 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++; - } + 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; + } - // 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 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; - - //Minimize controls. - this.controls.minimize = this.wrap.append('div').classed('cat-button cat-button--minimize hidden', true).attr('title', 'Hide controls').text('<<'); - this.controls.minimize.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.minimize.classed('hidden', true); - _this.controls.maximize.classed('hidden', false); - }); - - //Maximize controls. - this.controls.maximize = this.wrap.append('div').classed('cat-button cat-button--maximize hidden', true).attr('title', 'Show controls').text('>>'); - this.controls.maximize.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.controls.minimize.classed('hidden', false); - _this.controls.maximize.classed('hidden', true); - }); - } - - 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; + 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; } - } catch (err) { - _d = true; - _e = err; - } finally { - try { - if (!_n && _i["return"]) _i["return"](); - } finally { - if (_d) throw _e; + }; + + 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; + } + + 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 = ""; } - } - return _arr; + /* 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; } - 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"); - } - }; - }(); - - // 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; - } - }; - - 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; - } - - 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 = ""; - } - - /* 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(); - } - - 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) { - 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 { - cat.current.instance = window[cat.current.main]('.cat-chart .chart', cat.current.config); - cat.status.chartCreateStatus(cat.statusDiv, cat.current.main); - } + 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; + }); - cat.current.htmlExport = createChartExport(cat); // save the source code before init + 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; + }); - try { - cat.current.instance.init(data); - } catch (err) { - cat.status.chartInitStatus(cat.statusDiv, false, err); - } finally { - cat.status.chartInitStatus(cat.statusDiv, true, null, cat.current.htmlExport); + 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(); + } - // save to server button - if (cat.config.useServer) { - cat.status.saveToServer(cat); - } - showEnv(cat); + 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) { + 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 { + 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 { + cat.current.instance.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; + } + } + cat.current.rendered = true; + } - //don't print any new statuses until a new chart is rendered - cat.printStatus = false; - } - console.log(cat.current.instance); - } - } - - if (dataObject.user_loaded) { - dataObject.json = d3.csv.parse(dataObject.csv_raw); - render(false, dataObject.json); - console.log(cat.current.instance); - } else { - var dataFilePath = dataObject.path + dataFile; - d3.csv(dataFilePath, function (error, data) { - render(error, data); - console.log(cat.current.instance); - }); - } - } - - 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); - } - }); - } - - function loadLibrary(cat) { - var version = cat.controls.libraryVersion.node().value; - var library = 'webcharts'; //hardcode to webcharts for now - could generalize later - - // --- load css --- // - var cssPath = version !== 'master' ? cat.config.rootURL + '/Webcharts@' + version + '/css/webcharts.css' : cat.config.rootURL + '/Webcharts/css/webcharts.css'; - - 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); - } - - // --- load js --- // - var rendererPath = version !== 'master' ? cat.config.rootURL + '/' + library + '@' + version + '/build/webcharts.js' : cat.config.rootURL + '/Webcharts/build/webcharts.js'; - - 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); - }, - failure: function failure() { - cat.status.loadStatus(cat.statusDiv, false, rendererPath, library, version); - } - }); - } else { - cat.status.loadStatus(cat.statusDiv, true, rendererPath, library, version); - loadRenderer(cat); - } - } - - function addSubmitButton() { - var _this = this; - - this.controls.submitButton = this.controls.submitWrap.append('button').attr('class', 'submit').text('Render Chart').on('click', function () { - _this.previous = _this.current; - _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(); - - if (_this.previous) { - console.log(_this.previous); - _this.previous.instance.destroy(); - } - _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); - }); - } - - function initSubmit(cat) { - addControlsToggle.call(cat); - addSubmitButton.call(cat); - } - - 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 getVersions(select) { - var repo = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'https://api.github.com/repos/RhoInc/Webcharts'; - - var branches = fetch(repo + '/branches').then(function (response) { - return response.json(); - }); - var releases = fetch(repo + '/releases').then(function (response) { - return response.json(); - }); - - 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; - }); - select.selectAll('option').remove(); - select.selectAll('option').data(d3.merge(values)).enter().append('option').text(function (d) { - return d.name; - }).property('selected', function (d) { - return d.name === 'master'; - }); - }).catch(function (err) { - console.log(err); - }); - } - - function initRendererSelect(cat) { - cat.controls.rendererWrap.append('h3').text('1. Choose a Charting Library'); - cat.controls.rendererWrap.append('span').text('Library: '); - - //renderers - 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); - getVersions(cat.controls.versionSelect, cat.current.api_url); - }); - cat.controls.rendererWrap.append('br'); - - //renderer version - cat.controls.rendererWrap.append('span').text('Version: '); - cat.controls.versionSelect = cat.controls.rendererWrap.append('select'); - getVersions(cat.controls.versionSelect, cat.current.api_url); - //cat.controls.versionSelect.node().value = 'master'; - cat.controls.versionSelect.on('input', 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').on('click', function () { - d3.select(this).remove(); - cat.controls.rendererWrap.selectAll('*').classed('hidden', false); - }); - - //name of method that creates 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); - - //name of method that initializes chart - 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 version - cat.controls.rendererWrap.append('span').text('Webcharts Version: ').classed('hidden', true); - cat.controls.libraryVersion = cat.controls.rendererWrap.append('select').classed('hidden', true); - getVersions(cat.controls.libraryVersion); - //cat.controls.libraryVersion.node().value = 'master'; - cat.controls.rendererWrap.append('br').classed('hidden', true); - - //schema - 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; - }); - } - - 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); + 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); + }); + } + } - //add it to the select dropdown - cat.controls.dataFileSelect.append('option').datum(dataObject).text(function (d) { - return d.label; - }).attr('selected', true); + 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(f => f.link == cat.current.css_url); + //var css_loaded = current_css.length > 0; + //if (!css_loaded) { + cat.current.link = document.createElement('link'); + cat.current.link.href = cat.current.css_url; + + cat.current.link.type = 'text/css'; + cat.current.link.rel = 'stylesheet'; + document.getElementsByTagName('head')[0].appendChild(cat.current.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); + //} + } - //clear the file input & disable the load button - loadStatus.text(files[0].name + ' loaded').style('color', 'green'); + //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(); + cat.current.script = 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); + //} + }); + } + + function loadLibrary(cat) { + var version = cat.controls.libraryVersion.node().value; + var library = 'webcharts'; //hardcode to webcharts for now - could generalize later + + // --- load css --- // + var cssPath = + version !== 'master' + ? cat.config.rootURL + '/Webcharts@' + version + '/css/webcharts.css' + : cat.config.rootURL + '/Webcharts/css/webcharts.css'; + + 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); + } + + // --- load js --- // + var rendererPath = + version !== 'master' + ? cat.config.rootURL + '/' + library + '@' + version + '/build/webcharts.js' + : cat.config.rootURL + '/Webcharts/build/webcharts.js'; + + 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); + }, + failure: function failure() { + cat.status.loadStatus(cat.statusDiv, false, rendererPath, library, version); + } + }); + } else { + cat.status.loadStatus(cat.statusDiv, true, rendererPath, library, version); + loadRenderer(cat); + } + } + + 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(); + + if (_this.previous) { + console.log(_this.previous); + if (_this.previous.instance && _this.previous.instance.destroy) + _this.previous.instance.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(); + _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); + console.log(_this.current); + }); + } + + function initSubmit(cat) { + addControlsToggle.call(cat); + addSubmitButton.call(cat); + } + + function updateRenderer(select) { + var _this = this; + + this.previous = _.clone(this.current); + 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 getVersions(select) { + var repo = + arguments.length > 1 && arguments[1] !== undefined + ? arguments[1] + : 'https://api.github.com/repos/RhoInc/Webcharts'; + + var branches = fetch(repo + '/branches').then(function(response) { + return response.json(); + }); + var releases = fetch(repo + '/releases').then(function(response) { + return response.json(); + }); + + 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; + }); + select.selectAll('option').remove(); + select + .selectAll('option') + .data(d3.merge(values)) + .enter() + .append('option') + .text(function(d) { + return d.tag_name || d.name; + }) + .property('selected', function(d) { + return d.name === 'master'; + }); + }) + .catch(function(err) { + console.log(err); + }); + } + + function initRendererSelect(cat) { + cat.controls.rendererWrap.append('h3').text('1. Choose a Charting Library'); + cat.controls.rendererWrap.append('span').text('Library: '); + + //renderers + 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); + getVersions(cat.controls.versionSelect, cat.current.api_url); + }); + cat.controls.rendererWrap.append('br'); + + //renderer version + cat.controls.rendererWrap.append('span').text('Version: '); + cat.controls.versionSelect = cat.controls.rendererWrap.append('select'); + getVersions(cat.controls.versionSelect, cat.current.api_url); + //cat.controls.versionSelect.node().value = 'master'; + cat.controls.versionSelect.on('input', 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') + .on('click', function() { + d3.select(this).remove(); + cat.controls.rendererWrap.selectAll('*').classed('hidden', false); + }); + + //name of method that creates 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); + + //name of method that initializes chart + 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 version + cat.controls.rendererWrap + .append('span') + .text('Webcharts Version: ') + .classed('hidden', true); + cat.controls.libraryVersion = cat.controls.rendererWrap + .append('select') + .classed('hidden', true); + getVersions(cat.controls.libraryVersion); + //cat.controls.libraryVersion.node().value = 'master'; + cat.controls.rendererWrap.append('br').classed('hidden', true); + + //schema + 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); + } - cat.controls.dataFileLoadButton.attr('disabled', true); - cat.controls.dataFileLoad.property('value', ''); - }; + function showDataPreview(cat) { + cat.dataWrap.classed('hidden', false); + cat.chartWrap.classed('hidden', true); + cat.dataWrap.selectAll('*').remove(); - fr.readAsText(files.item(0)); - }); - } + if (cat.dataPreview) { + cat.dataPreview.destroy(); + } - function initChartConfig(cat) { - var settingsHeading = cat.controls.settingsWrap.append('h3').html('3. Customize the Chart '); + 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); + }); + } + } - cat.controls.settingsWrap.append('span').text('Settings: '); + 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; + }); + } + + 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)); + }); + } + + 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 ////////////////////////////////////// @@ -1170,387 +1430,496 @@ 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); - } - - 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); - } - - 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); - } - - 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(); - }); - }); - } - - /*------------------------------------------------------------------------------------------------\ + ////////////////////////////////////////////////////////////////////// + //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); + } + + 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); + } + + 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); + } + + 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: [] - }; - - 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; - }); - } - - 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); - } - - 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." - }]; - - var myStatus = statusOptions.filter(function (d) { - return d.key == statusVal; - })[0]; - - cat.controls.settingsStatus.html(myStatus.symbol).style('color', myStatus.color).attr('title', myStatus.details); - } - - 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; - } - - 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'); - - cat.controls.settingsTypeForm.property('checked', cat.current.settingsView == 'form').property('disabled', !cat.current.hasValidSchema); - - // Show/Hide sections - cat.controls.settingsInput.classed('hidden', cat.current.settingsView != 'text'); - cat.controls.settingsForm.classed('hidden', cat.current.settingsView != 'form'); - - //update the text or make the schema - cat.controls.settingsInput.node().value = JSON5.stringify(cat.current.config, null, 4); - - 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; - } - - // 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); - } + var controls = { + init: init$1, + addEnterEventListener: addEnterEventListener + }; - 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); - } - } - - 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); - } - } - - /*------------------------------------------------------------------------------------------------\ + var defaultSettings = { + useServer: false, + rootURL: null, + dataURL: null, + dataFiles: [], + renderers: [] + }; + + 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; + }); + } + + 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); + } + + 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." + } + ]; + + var myStatus = statusOptions.filter(function(d) { + return d.key == statusVal; + })[0]; + + cat.controls.settingsStatus + .html(myStatus.symbol) + .style('color', myStatus.color) + .attr('title', myStatus.details); + } + + 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; + } + + 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'); + + cat.controls.settingsTypeForm + .property('checked', cat.current.settingsView == 'form') + .property('disabled', !cat.current.hasValidSchema); + + // Show/Hide sections + cat.controls.settingsInput.classed('hidden', cat.current.settingsView != 'text'); + cat.controls.settingsForm.classed('hidden', cat.current.settingsView != 'form'); + + //update the text or make the schema + cat.controls.settingsInput.node().value = JSON5.stringify(cat.current.config, null, 4); + + 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; + } + + // 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); + } + } + + 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); + } + } + + /*------------------------------------------------------------------------------------------------\ 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); - } - } - - 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.'); - }); - }); - } - - 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); - } - - /*------------------------------------------------------------------------------------------------\ + 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); + } + } + + 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.'); + }); + }); + } + + 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 - }; - - 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; - -}))); + var status = { + chartCreateStatus: chartCreateStatus, + chartInitStatus: chartInitStatus, + saveToServer: saveToServer, + loadStatus: loadStatus + }; + + 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; +}); diff --git a/src/cat/controls/initRendererSelect/getVersions.js b/src/cat/controls/initRendererSelect/getVersions.js index 6be49ff..ced9b2b 100644 --- a/src/cat/controls/initRendererSelect/getVersions.js +++ b/src/cat/controls/initRendererSelect/getVersions.js @@ -18,7 +18,7 @@ export default function getVersions( .data(d3.merge(values)) .enter() .append('option') - .text(d => d.name) + .text(d => d.tag_name || d.name) .property('selected', d => d.name === 'master'); }) .catch(err => { 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/addSubmitButton.js b/src/cat/controls/initSubmit/addSubmitButton.js index 5b532b9..46bb767 100644 --- a/src/cat/controls/initSubmit/addSubmitButton.js +++ b/src/cat/controls/initSubmit/addSubmitButton.js @@ -6,7 +6,6 @@ export default function addSubmitButton() { .attr('class', 'submit') .text('Render Chart') .on('click', () => { - this.previous = this.current; this.controls.minimize.classed('hidden', false); this.dataWrap.classed('hidden', true); this.chartWrap.classed('hidden', false); @@ -27,8 +26,26 @@ export default function addSubmitButton() { if (this.previous) { console.log(this.previous); - this.previous.instance.destroy(); + if (this.previous.instance && this.previous.instance.destroy) + this.previous.instance.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(); this.printStatus = true; this.statusDiv = this.chartWrap.append('div').attr('class', 'status'); @@ -39,5 +56,6 @@ export default function addSubmitButton() { this.chartWrap.append('div').attr('class', 'chart'); loadLibrary(this); + console.log(this.current); }); } diff --git a/src/cat/loadRenderer.js b/src/cat/loadRenderer.js index 144691d..b7b2e1c 100644 --- a/src/cat/loadRenderer.js +++ b/src/cat/loadRenderer.js @@ -15,62 +15,62 @@ export function loadRenderer(cat) { 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; + //var current_css = getCSS().filter(f => f.link == cat.current.css_url); + //var css_loaded = current_css.length > 0; + //if (!css_loaded) { + cat.current.link = document.createElement('link'); + cat.current.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); - } + cat.current.link.type = 'text/css'; + cat.current.link.rel = 'stylesheet'; + document.getElementsByTagName('head')[0].appendChild(cat.current.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; + //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); - } + //if (!js_loaded) { + var loader = new scriptLoader(); + cat.current.script = 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); + //} }); } diff --git a/src/cat/renderChart.js b/src/cat/renderChart.js index 45e5353..8e0291c 100644 --- a/src/cat/renderChart.js +++ b/src/cat/renderChart.js @@ -23,7 +23,10 @@ export function renderChart(cat) { ); cat.status.chartCreateStatus(cat.statusDiv, cat.current.main, cat.current.sub); } else { - cat.current.instance = 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); } @@ -45,19 +48,17 @@ export function renderChart(cat) { //don't print any new statuses until a new chart is rendered cat.printStatus = false; } - console.log(cat.current.instance); } + cat.current.rendered = true; } if (dataObject.user_loaded) { dataObject.json = d3.csv.parse(dataObject.csv_raw); render(false, dataObject.json); - console.log(cat.current.instance); } else { var dataFilePath = dataObject.path + dataFile; d3.csv(dataFilePath, function(error, data) { render(error, data); - console.log(cat.current.instance); }); } } diff --git a/src/util/scriptLoader.js b/src/util/scriptLoader.js index f7a9c1c..656223c 100644 --- a/src/util/scriptLoader.js +++ b/src/util/scriptLoader.js @@ -100,6 +100,6 @@ scriptLoader.prototype = { } }, 1); }, typeof args.delay == 'number' ? args.delay : 1); - return true; + return scriptTag; } }; From 32d6e9d335932aa7990c1439184fa1029a208d33 Mon Sep 17 00:00:00 2001 From: Spencer Date: Fri, 24 May 2019 17:29:02 -0400 Subject: [PATCH 5/8] test out editable table --- build/cat.js | 3343 ++++++++--------- index.html | 2 + package-lock.json | 92 + package.json | 1 + .../controls/initSubmit/addSubmitButton.js | 41 +- src/cat/init.js | 2 + test-page/handsontable/index.css | 33 + test-page/handsontable/index.html | 27 + test-page/handsontable/index.js | 63 + 9 files changed, 1743 insertions(+), 1861 deletions(-) create mode 100644 test-page/handsontable/index.css create mode 100644 test-page/handsontable/index.html create mode 100644 test-page/handsontable/index.js diff --git a/build/cat.js b/build/cat.js index ed52b46..9ce388e 100644 --- a/build/cat.js +++ b/build/cat.js @@ -1,1424 +1,1190 @@ -(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); - }); - } - ); - } +(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; + // Store setTimeout reference so promise-polyfill will be unaffected by + // other code modifying setTimeout (like sinon.useFakeTimers()) + var setTimeoutFunc = setTimeout; - function noop() {} + function noop() {} - // Polyfill for Function.prototype.bind - function bind(fn, thisArg) { - return function() { - fn.apply(thisArg, arguments); - }; + // 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; } - - /** - * @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); + if (self._state === 0) { + self._deferreds.push(deferred); + return; } - - 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 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._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); - }); + } + self._state = 1; + self._value = newValue; + finale(self); + } catch (e) { + reject(self, e); } - - 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); } + }); } - function reject(self, newValue) { - self._state = 2; - self._value = newValue; - finale(self); + for (var i = 0, len = self._deferreds.length; i < len; i++) { + handle(self, self._deferreds[i]); } - - 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); + 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['catch'] = function(onRejected) { + return this.then(null, onRejected); + }; - 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); - } - } + Promise$1.prototype.then = function(onFulfilled, onRejected) { + // @ts-ignore + var prom = new this.constructor(noop); - for (var i = 0; i < args.length; i++) { - res(i, args[i]); - } - }); - }; + handle(this, new Handler(onFulfilled, onRejected, prom)); + return prom; + }; - Promise$1.resolve = function(value) { - if (value && typeof value === 'object' && value.constructor === Promise$1) { - return value; - } + Promise$1.prototype['finally'] = finallyConstructor; - return new Promise$1(function(resolve) { - resolve(value); - }); - }; + 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; - 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); + 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; } - }); - }; - - // 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 - } - }; - - /** @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; + } + args[i] = val; + if (--remaining === 0) { + resolve(args); + } + } catch (ex) { + reject(ex); } - 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; - } + for (var i = 0; i < args.length; i++) { + res(i, args[i]); + } + }); + }; - 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 - }); + Promise$1.resolve = function(value) { + if (value && typeof value === 'object' && value.constructor === Promise$1) { + return 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; - } - }); - } + 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); + }; - 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; - } - }); + Promise$1._unhandledRejectionFn = function _unhandledRejectionFn(err) { + if (typeof console !== 'undefined' && console) { + console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console } - - 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); + }; + + /** @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 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); + if (typeof window !== 'undefined') { + return window; } - - function addControlsToggle() { - 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; - - //Minimize controls. - this.controls.minimize = this.wrap - .append('div') - .classed('cat-button cat-button--minimize hidden', true) - .attr('title', 'Hide controls') - .text('<<'); - this.controls.minimize.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.minimize.classed('hidden', true); - _this.controls.maximize.classed('hidden', false); - }); - - //Maximize controls. - this.controls.maximize = this.wrap - .append('div') - .classed('cat-button cat-button--maximize hidden', true) - .attr('title', 'Show controls') - .text('>>'); - this.controls.maximize.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.controls.minimize.classed('hidden', false); - _this.controls.maximize.classed('hidden', true); - }); + if (typeof global !== 'undefined') { + return global; } - - var _typeof = - typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol' - ? function(obj) { - return typeof obj; + 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(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; - } - } catch (err) { - _d = true; - _e = err; - } finally { - try { - if (!_n && _i['return']) _i['return'](); - } finally { - if (_d) throw _e; - } - } - - return _arr; - } - - 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'); - } - }; - })(); - - // 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; - } - }; + var to = Object(target); - 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; - } - - 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; - } + for (var index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; - 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 = ""; - } + 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 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; - } + 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'); + } - 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; - }); + var o = Object(this); - 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; - }); + // 2. Let len be ? ToLength(? Get(O, 'length')). + var len = o.length >>> 0; - 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(); - } + // 3. If IsCallable(predicate) is false, throw a TypeError exception. + if (typeof predicate !== 'function') { + throw new TypeError('predicate must be a function'); + } - 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) { - 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 { - 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 { - cat.current.instance.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; - } - } - cat.current.rendered = 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 kValue. + var kValue = o[k]; + if (predicate.call(thisArg, kValue, k, o)) { + return kValue; + } + // e. Increase k by 1. + k++; + } - 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); - }); - } - } + // 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'); + } - 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(f => f.link == cat.current.css_url); - //var css_loaded = current_css.length > 0; - //if (!css_loaded) { - cat.current.link = document.createElement('link'); - cat.current.link.href = cat.current.css_url; - - cat.current.link.type = 'text/css'; - cat.current.link.rel = 'stylesheet'; - document.getElementsByTagName('head')[0].appendChild(cat.current.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 o = Object(this); - //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(); - cat.current.script = 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); - //} - }); - } + // 2. Let len be ? ToLength(? Get(O, "length")). + var len = o.length >>> 0; - function loadLibrary(cat) { - var version = cat.controls.libraryVersion.node().value; - var library = 'webcharts'; //hardcode to webcharts for now - could generalize later + // 3. If IsCallable(predicate) is false, throw a TypeError exception. + if (typeof predicate !== 'function') { + throw new TypeError('predicate must be a function'); + } - // --- load css --- // - var cssPath = - version !== 'master' - ? cat.config.rootURL + '/Webcharts@' + version + '/css/webcharts.css' - : cat.config.rootURL + '/Webcharts/css/webcharts.css'; + // 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++; + } - 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); + // 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); + + console.log(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 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; + + //Minimize controls. + this.controls.minimize = this.wrap.append('div').classed('cat-button cat-button--minimize hidden', true).attr('title', 'Hide controls').text('<<'); + this.controls.minimize.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.minimize.classed('hidden', true); + _this.controls.maximize.classed('hidden', false); + }); + + //Maximize controls. + this.controls.maximize = this.wrap.append('div').classed('cat-button cat-button--maximize hidden', true).attr('title', 'Show controls').text('>>'); + this.controls.maximize.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.controls.minimize.classed('hidden', false); + _this.controls.maximize.classed('hidden', true); + }); + } + + 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; } - - // --- load js --- // - var rendererPath = - version !== 'master' - ? cat.config.rootURL + '/' + library + '@' + version + '/build/webcharts.js' - : cat.config.rootURL + '/Webcharts/build/webcharts.js'; - - 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); - }, - failure: function failure() { - cat.status.loadStatus(cat.statusDiv, false, rendererPath, library, version); - } - }); - } else { - cat.status.loadStatus(cat.statusDiv, true, rendererPath, library, version); - loadRenderer(cat); + } catch (err) { + _d = true; + _e = err; + } finally { + try { + if (!_n && _i["return"]) _i["return"](); + } finally { + if (_d) throw _e; } - } + } - 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(); - - if (_this.previous) { - console.log(_this.previous); - if (_this.previous.instance && _this.previous.instance.destroy) - _this.previous.instance.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(); - _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); - console.log(_this.current); - }); + return _arr; } - function initSubmit(cat) { - addControlsToggle.call(cat); - addSubmitButton.call(cat); - } - - function updateRenderer(select) { - var _this = this; - - this.previous = _.clone(this.current); - 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 getVersions(select) { - var repo = - arguments.length > 1 && arguments[1] !== undefined - ? arguments[1] - : 'https://api.github.com/repos/RhoInc/Webcharts'; + 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"); + } + }; + }(); + + // 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 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; + } + + 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 = ""; + } + + /* 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(); + } + + 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) { + 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 { + cat.current.instance = window[cat.current.main]('.cat-chart .chart', cat.current.config); + cat.status.chartCreateStatus(cat.statusDiv, cat.current.main); + } - var branches = fetch(repo + '/branches').then(function(response) { - return response.json(); - }); - var releases = fetch(repo + '/releases').then(function(response) { - return response.json(); - }); + cat.current.htmlExport = createChartExport(cat); // save the source code before init - 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; - }); - select.selectAll('option').remove(); - select - .selectAll('option') - .data(d3.merge(values)) - .enter() - .append('option') - .text(function(d) { - return d.tag_name || d.name; - }) - .property('selected', function(d) { - return d.name === 'master'; - }); - }) - .catch(function(err) { - console.log(err); - }); - } + try { + cat.current.instance.init(data); + } catch (err) { + cat.status.chartInitStatus(cat.statusDiv, false, err); + } finally { + cat.status.chartInitStatus(cat.statusDiv, true, null, cat.current.htmlExport); - function initRendererSelect(cat) { - cat.controls.rendererWrap.append('h3').text('1. Choose a Charting Library'); - cat.controls.rendererWrap.append('span').text('Library: '); - - //renderers - 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); - getVersions(cat.controls.versionSelect, cat.current.api_url); - }); - cat.controls.rendererWrap.append('br'); - - //renderer version - cat.controls.rendererWrap.append('span').text('Version: '); - cat.controls.versionSelect = cat.controls.rendererWrap.append('select'); - getVersions(cat.controls.versionSelect, cat.current.api_url); - //cat.controls.versionSelect.node().value = 'master'; - cat.controls.versionSelect.on('input', 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') - .on('click', function() { - d3.select(this).remove(); - cat.controls.rendererWrap.selectAll('*').classed('hidden', false); - }); - - //name of method that creates 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); - - //name of method that initializes chart - 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 version - cat.controls.rendererWrap - .append('span') - .text('Webcharts Version: ') - .classed('hidden', true); - cat.controls.libraryVersion = cat.controls.rendererWrap - .append('select') - .classed('hidden', true); - getVersions(cat.controls.libraryVersion); - //cat.controls.libraryVersion.node().value = 'master'; - cat.controls.rendererWrap.append('br').classed('hidden', true); - - //schema - 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); - } + // save to server button + if (cat.config.useServer) { + cat.status.saveToServer(cat); + } + showEnv(cat); - function showDataPreview(cat) { - cat.dataWrap.classed('hidden', false); - cat.chartWrap.classed('hidden', true); - cat.dataWrap.selectAll('*').remove(); + //don't print any new statuses until a new chart is rendered + cat.printStatus = false; + } + } + cat.current.rendered = true; + } + + 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); + }); + } + } + + 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(f => f.link == cat.current.css_url); + //var css_loaded = current_css.length > 0; + //if (!css_loaded) { + cat.current.link = document.createElement('link'); + cat.current.link.href = cat.current.css_url; + + cat.current.link.type = 'text/css'; + cat.current.link.rel = 'stylesheet'; + document.getElementsByTagName('head')[0].appendChild(cat.current.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(); + cat.current.script = 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); + //} + }); + } + + function loadLibrary(cat) { + var version = cat.controls.libraryVersion.node().value; + var library = 'webcharts'; //hardcode to webcharts for now - could generalize later + + // --- load css --- // + var cssPath = version !== 'master' ? cat.config.rootURL + '/Webcharts@' + version + '/css/webcharts.css' : cat.config.rootURL + '/Webcharts/css/webcharts.css'; + + 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); + } + + // --- load js --- // + var rendererPath = version !== 'master' ? cat.config.rootURL + '/' + library + '@' + version + '/build/webcharts.js' : cat.config.rootURL + '/Webcharts/build/webcharts.js'; + + 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); + }, + failure: function failure() { + cat.status.loadStatus(cat.statusDiv, false, rendererPath, library, version); + } + }); + } else { + cat.status.loadStatus(cat.statusDiv, true, rendererPath, library, version); + loadRenderer(cat); + } + } + + 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(); + + 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(); + _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); + console.log(_this.current); + }); + } + + function initSubmit(cat) { + addControlsToggle.call(cat); + addSubmitButton.call(cat); + } + + function updateRenderer(select) { + var _this = this; + + this.previous = _.clone(this.current); + 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 getVersions(select) { + var repo = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'https://api.github.com/repos/RhoInc/Webcharts'; + + var branches = fetch(repo + '/branches').then(function (response) { + return response.json(); + }); + var releases = fetch(repo + '/releases').then(function (response) { + return response.json(); + }); + + 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; + }); + select.selectAll('option').remove(); + select.selectAll('option').data(d3.merge(values)).enter().append('option').text(function (d) { + return d.tag_name || d.name; + }).property('selected', function (d) { + return d.name === 'master'; + }); + }).catch(function (err) { + console.log(err); + }); + } + + function initRendererSelect(cat) { + cat.controls.rendererWrap.append('h3').text('1. Choose a Charting Library'); + cat.controls.rendererWrap.append('span').text('Library: '); + + //renderers + 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); + getVersions(cat.controls.versionSelect, cat.current.api_url); + }); + cat.controls.rendererWrap.append('br'); + + //renderer version + cat.controls.rendererWrap.append('span').text('Version: '); + cat.controls.versionSelect = cat.controls.rendererWrap.append('select'); + getVersions(cat.controls.versionSelect, cat.current.api_url); + //cat.controls.versionSelect.node().value = 'master'; + cat.controls.versionSelect.on('input', 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').on('click', function () { + d3.select(this).remove(); + cat.controls.rendererWrap.selectAll('*').classed('hidden', false); + }); + + //name of method that creates 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); + + //name of method that initializes chart + 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 version + cat.controls.rendererWrap.append('span').text('Webcharts Version: ').classed('hidden', true); + cat.controls.libraryVersion = cat.controls.rendererWrap.append('select').classed('hidden', true); + getVersions(cat.controls.libraryVersion); + //cat.controls.libraryVersion.node().value = 'master'; + cat.controls.rendererWrap.append('br').classed('hidden', true); + + //schema + 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; + }); + } + + 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); - if (cat.dataPreview) { - cat.dataPreview.destroy(); - } + //add it to the select dropdown + cat.controls.dataFileSelect.append('option').datum(dataObject).text(function (d) { + return d.label; + }).attr('selected', true); - 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); - }); - } - } + //clear the file input & disable the load button + loadStatus.text(files[0].name + ' loaded').style('color', 'green'); - 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; - }); - } + cat.controls.dataFileLoadButton.attr('disabled', true); + cat.controls.dataFileLoad.property('value', ''); + }; - 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)); - }); - } + fr.readAsText(files.item(0)); + }); + } - function initChartConfig(cat) { - var settingsHeading = cat.controls.settingsWrap - .append('h3') - .html('3. Customize the Chart '); + function initChartConfig(cat) { + var settingsHeading = cat.controls.settingsWrap.append('h3').html('3. Customize the Chart '); - cat.controls.settingsWrap.append('span').text('Settings: '); + cat.controls.settingsWrap.append('span').text('Settings: '); - /* + /* ////////////////////////////////////// //initialize the config status icon ////////////////////////////////////// @@ -1430,496 +1196,387 @@ 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); - } - - 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); - } - - 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); - } - - 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(); - }); - }); - } - - /*------------------------------------------------------------------------------------------------\ + ////////////////////////////////////////////////////////////////////// + //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); + } + + 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); + } + + 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); + } + + 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: [] - }; - - 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; - }); - } - - 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); - } - - 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." - } - ]; - - var myStatus = statusOptions.filter(function(d) { - return d.key == statusVal; - })[0]; - - cat.controls.settingsStatus - .html(myStatus.symbol) - .style('color', myStatus.color) - .attr('title', myStatus.details); - } - - 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; - } - - 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'); - - cat.controls.settingsTypeForm - .property('checked', cat.current.settingsView == 'form') - .property('disabled', !cat.current.hasValidSchema); - - // Show/Hide sections - cat.controls.settingsInput.classed('hidden', cat.current.settingsView != 'text'); - cat.controls.settingsForm.classed('hidden', cat.current.settingsView != 'form'); - - //update the text or make the schema - cat.controls.settingsInput.node().value = JSON5.stringify(cat.current.config, null, 4); - - 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; - } - - // 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); - } - } - - 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); - } - } + var controls = { + init: init$1, + addEnterEventListener: addEnterEventListener + }; + + var defaultSettings = { + useServer: false, + rootURL: null, + dataURL: null, + dataFiles: [], + renderers: [] + }; + + 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; + }); + } + + 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); + } + + 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." + }]; + + var myStatus = statusOptions.filter(function (d) { + return d.key == statusVal; + })[0]; + + cat.controls.settingsStatus.html(myStatus.symbol).style('color', myStatus.color).attr('title', myStatus.details); + } + + 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; + } + + 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'); + + cat.controls.settingsTypeForm.property('checked', cat.current.settingsView == 'form').property('disabled', !cat.current.hasValidSchema); + + // Show/Hide sections + cat.controls.settingsInput.classed('hidden', cat.current.settingsView != 'text'); + cat.controls.settingsForm.classed('hidden', cat.current.settingsView != 'form'); + + //update the text or make the schema + cat.controls.settingsInput.node().value = JSON5.stringify(cat.current.config, null, 4); + + 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; + } + + // 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); + } + } + + 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); + } + } + + /*------------------------------------------------------------------------------------------------\ 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); - } - } - - 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.'); - }); - }); - } - - 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); - } - - /*------------------------------------------------------------------------------------------------\ + 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); + } + } + + 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.'); + }); + }); + } + + 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 - }; - - 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; -}); + var status = { + chartCreateStatus: chartCreateStatus, + chartInitStatus: chartInitStatus, + saveToServer: saveToServer, + loadStatus: loadStatus + }; + + 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; + +}))); diff --git a/index.html b/index.html index 4b9e1f8..0f94a19 100644 --- a/index.html +++ b/index.html @@ -11,6 +11,7 @@ + @@ -24,6 +25,7 @@ + diff --git a/package-lock.json b/package-lock.json index b7efdc6..c1b0f54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,20 @@ "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", @@ -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 bd0d577..8dada60 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "body-parser": "~1", "d3": "^3.5.14", "express": "~4", + "handsontable": "^7.0.3", "jquery": "^3.4.1", "json5": "^0.5.1", "webcharts": "~1" diff --git a/src/cat/controls/initSubmit/addSubmitButton.js b/src/cat/controls/initSubmit/addSubmitButton.js index 46bb767..d964a09 100644 --- a/src/cat/controls/initSubmit/addSubmitButton.js +++ b/src/cat/controls/initSubmit/addSubmitButton.js @@ -25,25 +25,30 @@ export default function addSubmitButton() { .remove(); if (this.previous) { - console.log(this.previous); - if (this.previous.instance && this.previous.instance.destroy) - this.previous.instance.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(); - } + 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(); - } - }); + //unmount chart wrapper + chart.wrap.remove(); + } + }); + } } this.chartWrap.selectAll('*').remove(); diff --git a/src/cat/init.js b/src/cat/init.js index 563002c..01d7b35 100644 --- a/src/cat/init.js +++ b/src/cat/init.js @@ -13,4 +13,6 @@ export function init() { //create the controls this.controls.init(this); + + console.log(this); } 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..193c54e --- /dev/null +++ b/test-page/handsontable/index.html @@ -0,0 +1,27 @@ + + + + Handsontable: Test Page + + + + + + + + + + + + + +
Handsontable
+
Test Page
+
+
+
+
+ + + + diff --git a/test-page/handsontable/index.js b/test-page/handsontable/index.js new file mode 100644 index 0000000..481b9f8 --- /dev/null +++ b/test-page/handsontable/index.js @@ -0,0 +1,63 @@ +fetch('https://raw.githubusercontent.com/RhoInc/data-library/master/data/miscellaneous/iris.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 chart = new webCharts.createChart( + document.getElementById('chart'), + chartSettings, + ); + const data = d3.csv.parse(text); + chart.init(data); + const colHeaders = Object.keys(data[0]); + const table = new Handsontable( + document.getElementById('table'), + { + data, + colHeaders, + //columns: colHeaders.map(_ => { return {type: 'text'}; }), + 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); + }, + licenseKey: 'non-commercial-and-evaluation', + }, + ); + }); From 6d01a1a288026e5b01a1f76f932c8166bcfe52d8 Mon Sep 17 00:00:00 2001 From: Spencer Date: Thu, 30 May 2019 17:54:24 -0400 Subject: [PATCH 6/8] update README --- README.md | 68 +++++++++ build/cat.js | 65 +++++++-- index.html | 4 +- src/cat/controls/initDataSelect.js | 2 +- src/cat/controls/initRendererSelect.js | 7 +- .../controls/initSubmit/addSubmitButton.js | 1 + src/cat/datapreview/showDataPreview.js | 52 ++++++- src/cat/init.js | 2 - src/cat/renderChart.js | 7 +- test-page/handsontable/index.html | 4 +- test-page/handsontable/index.js | 129 +++++++++++------- 11 files changed, 263 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index d2459a2..c0641dc 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,70 @@ # CAT 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 renderer library/version and charting library/versoin 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 renderer. + +#### Library: +dropdown with a list of charting libraries + +1. Loads the selected version of the selected renderer 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. + 3. Loads the main .js file. + 4. Loads the main .css file if the library has a .css file. +2. Updates the settings text/form. + +#### Version: +dropdown with a list of branches and releases for the selected library + +1. Loads the selected version of the selected renderer 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. + 3. Loads the main .js file. + 4. Loads the main .css file if the library has a .css file. +2. Updates the settings text/form. + +#### Init: +input that allows the specification of the namespace of the selected library + +#### . +optional input that allows the specification of the method that generates the chart + +#### Webcharts Version: +dropdown with a list of branches and releases of the charting library; Webcharts is currently the only charting library supported + +1. Loads the selected charting 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 renderer library + +### 2. Choose a Dataset +controls the selected data file + +#### :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 library's settings schema + +#### Settings:-text +a simple text input that allows the specification of a settings object + +#### Settings:-form +a list of inputs generated with the settings schema of the loaded charting library + +### 4. Environment +a list of the loaded stylesheets and JavaScript files diff --git a/build/cat.js b/build/cat.js index 9ce388e..d037cea 100644 --- a/build/cat.js +++ b/build/cat.js @@ -415,8 +415,6 @@ //create the controls this.controls.init(this); - - console.log(this); } function layout(cat) { @@ -785,13 +783,14 @@ 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); }); } } @@ -919,6 +918,7 @@ d3.selectAll('style').property('disabled', true).remove(); + console.log(_this.previous); if (_this.previous) { if (_this.previous.instance && _this.previous.instance.destroy) { console.log('destroy'); @@ -1031,11 +1031,12 @@ cat.controls.versionSelect = cat.controls.rendererWrap.append('select'); getVersions(cat.controls.versionSelect, cat.current.api_url); //cat.controls.versionSelect.node().value = 'master'; - cat.controls.versionSelect.on('input', function () { + //cat.controls.versionSelect.on('input', function() { + // console.log(this.value); + //}); + cat.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'); @@ -1094,19 +1095,59 @@ 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'); + 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)); + 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) { + var colHeaders = Object.keys(data[0]); + var table = new Handsontable(cat.dataWrap.select('.dataPreview').node(), { + data: data.map(function (d) { + return Object.keys(d).map(function (key) { + return d[key]; + }); + }), + colHeaders: colHeaders, + columns: colHeaders.map(function (_) { + return { type: 'text' }; + }), + rowHeaders: true, + dropdownMenu: true, + filters: true, + afterChange: function afterChange(changes) { + dataObject.json = this.getData().map(function (d) { + return d.reduce(function (acc, cur, i) { + acc[colHeaders[i]] = cur; + return acc; + }, {}); + }); + }, + afterFilter: function afterFilter(changes) { + dataObject.json = this.getData().map(function (d) { + return d.reduce(function (acc, cur, i) { + acc[colHeaders[i]] = cur; + return acc; + }, {}); + }); + }, + licenseKey: 'non-commercial-and-evaluation' }); } } function initDataSelect(cat) { - cat.controls.dataWrap.append('h3').text('2. Choose a data Set'); + cat.controls.dataWrap.append('h3').text('2. Choose a Dataset'); cat.controls.dataFileSelect = cat.controls.dataWrap.append('select'); cat.controls.dataWrap.append('span').html('🔍').style('cursor', 'pointer').on('click', function () { diff --git a/index.html b/index.html index 0f94a19..aada08e 100644 --- a/index.html +++ b/index.html @@ -11,7 +11,7 @@ - + @@ -25,7 +25,7 @@ - + diff --git a/src/cat/controls/initDataSelect.js b/src/cat/controls/initDataSelect.js index f708266..d522ed1 100644 --- a/src/cat/controls/initDataSelect.js +++ b/src/cat/controls/initDataSelect.js @@ -1,7 +1,7 @@ import { showDataPreview } from '../datapreview/showDataPreview'; export function initDataSelect(cat) { - cat.controls.dataWrap.append('h3').text('2. Choose a data Set'); + cat.controls.dataWrap.append('h3').text('2. Choose a Dataset'); cat.controls.dataFileSelect = cat.controls.dataWrap.append('select'); cat.controls.dataWrap diff --git a/src/cat/controls/initRendererSelect.js b/src/cat/controls/initRendererSelect.js index be0d95f..efb4b4f 100644 --- a/src/cat/controls/initRendererSelect.js +++ b/src/cat/controls/initRendererSelect.js @@ -24,11 +24,12 @@ export function initRendererSelect(cat) { cat.controls.versionSelect = cat.controls.rendererWrap.append('select'); getVersions(cat.controls.versionSelect, cat.current.api_url); //cat.controls.versionSelect.node().value = 'master'; - cat.controls.versionSelect.on('input', function() { + //cat.controls.versionSelect.on('input', function() { + // console.log(this.value); + //}); + cat.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'); diff --git a/src/cat/controls/initSubmit/addSubmitButton.js b/src/cat/controls/initSubmit/addSubmitButton.js index d964a09..7c82d95 100644 --- a/src/cat/controls/initSubmit/addSubmitButton.js +++ b/src/cat/controls/initSubmit/addSubmitButton.js @@ -24,6 +24,7 @@ export default function addSubmitButton() { .property('disabled', true) .remove(); + console.log(this.previous); if (this.previous) { if (this.previous.instance && this.previous.instance.destroy) { console.log('destroy'); 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/init.js b/src/cat/init.js index 01d7b35..563002c 100644 --- a/src/cat/init.js +++ b/src/cat/init.js @@ -13,6 +13,4 @@ export function init() { //create the controls this.controls.init(this); - - console.log(this); } diff --git a/src/cat/renderChart.js b/src/cat/renderChart.js index 8e0291c..a9b8547 100644 --- a/src/cat/renderChart.js +++ b/src/cat/renderChart.js @@ -52,13 +52,16 @@ export function renderChart(cat) { 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/test-page/handsontable/index.html b/test-page/handsontable/index.html index 193c54e..26e79f0 100644 --- a/test-page/handsontable/index.html +++ b/test-page/handsontable/index.html @@ -1,7 +1,7 @@ - Handsontable: Test Page + Hands-on Table: Test Page @@ -15,7 +15,7 @@ -
Handsontable
+
Hands-on Table
Test Page
diff --git a/test-page/handsontable/index.js b/test-page/handsontable/index.js index 481b9f8..55b08b5 100644 --- a/test-page/handsontable/index.js +++ b/test-page/handsontable/index.js @@ -1,62 +1,91 @@ -fetch('https://raw.githubusercontent.com/RhoInc/data-library/master/data/miscellaneous/iris.csv') +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 chart = new webCharts.createChart( - document.getElementById('chart'), - chartSettings, - ); + //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); - chart.init(data); + console.log(data); + //chart.init(data); const colHeaders = Object.keys(data[0]); const table = new Handsontable( document.getElementById('table'), { - data, + data: data.map(d => Object.keys(d).map(key => d[key])), colHeaders, - //columns: colHeaders.map(_ => { return {type: 'text'}; }), + 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); - }, + //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', }, ); From a7d0d719c58e92cb51ae3c5dcfc0b5ad16445c6f Mon Sep 17 00:00:00 2001 From: Spencer Childress Date: Fri, 31 May 2019 18:08:37 -0400 Subject: [PATCH 7/8] refactoring sucks --- README.md | 80 +- build/cat.js | 899 +++++++++--------- css/cat.css | 6 +- src/cat/controls.js | 2 +- src/cat/controls/init.js | 18 +- src/cat/controls/initChartConfig.js | 60 +- src/cat/controls/initDataSelect.js | 22 +- src/cat/controls/initEnvConfig.js | 12 +- src/cat/controls/initFileLoad.js | 32 +- src/cat/controls/initRendererSelect.js | 103 +- .../controls/initRendererSelect/loadDOM.js | 2 + .../controls/initRendererSelect/unloadDOM.js | 14 + src/cat/controls/initSubmit.js | 27 +- .../controls/initSubmit/addSubmitButton.js | 67 -- src/cat/controls/initSubmit/destroyChart.js | 30 + .../controls/initSubmit/initializeChart.js | 42 + src/cat/controls/initSubmit/loadData.js | 8 + src/cat/controls/initSubmit/updateStatus.js | 8 + src/cat/init.js | 36 +- src/cat/init/getVersions.js | 27 + src/cat/init/loadPackageJSON.js | 29 + src/cat/init/loadRenderer.js | 42 + src/cat/init/loadWebcharts.js | 35 + src/cat/init/updateSettings.js | 2 + src/cat/layout.js | 52 +- src/cat/layout/chart.js | 0 src/cat/layout/controls.js | 17 + .../layout/controls/chooseAChartingLibrary.js | 19 + .../controls/chooseAChartingLibrary/init.js | 13 + .../chooseAChartingLibrary/library.js | 11 + .../chooseAChartingLibrary/moreOptions.js | 8 + .../controls/chooseAChartingLibrary/schema.js | 8 + .../chooseAChartingLibrary/version.js | 5 + .../webchartsVersion.js | 10 + src/cat/layout/controls/chooseADataset.js | 15 + .../controls/chooseADataset/dataFile.js | 9 + .../controls/chooseADataset/loadADataFile.js | 27 + src/cat/layout/controls/customizeTheChart.js | 32 + src/cat/layout/controls/environment.js | 10 + src/cat/layout/controls/renderChart.js | 9 + src/cat/layout/table.js | 0 .../toggleDisplayOfControls.js} | 28 +- src/cat/loadLibrary.js | 2 +- src/cat/loadRenderer.js | 2 +- src/cat/setDefaults.js | 18 +- src/cat/settings.js | 2 +- src/cat/status.js | 2 +- src/createCat.js | 26 +- src/util/scriptLoader.js | 2 +- 49 files changed, 1067 insertions(+), 863 deletions(-) create mode 100644 src/cat/controls/initRendererSelect/loadDOM.js create mode 100644 src/cat/controls/initRendererSelect/unloadDOM.js delete mode 100644 src/cat/controls/initSubmit/addSubmitButton.js create mode 100644 src/cat/controls/initSubmit/destroyChart.js create mode 100644 src/cat/controls/initSubmit/initializeChart.js create mode 100644 src/cat/controls/initSubmit/loadData.js create mode 100644 src/cat/controls/initSubmit/updateStatus.js create mode 100644 src/cat/init/getVersions.js create mode 100644 src/cat/init/loadPackageJSON.js create mode 100644 src/cat/init/loadRenderer.js create mode 100644 src/cat/init/loadWebcharts.js create mode 100644 src/cat/init/updateSettings.js create mode 100644 src/cat/layout/chart.js create mode 100644 src/cat/layout/controls.js create mode 100644 src/cat/layout/controls/chooseAChartingLibrary.js create mode 100644 src/cat/layout/controls/chooseAChartingLibrary/init.js create mode 100644 src/cat/layout/controls/chooseAChartingLibrary/library.js create mode 100644 src/cat/layout/controls/chooseAChartingLibrary/moreOptions.js create mode 100644 src/cat/layout/controls/chooseAChartingLibrary/schema.js create mode 100644 src/cat/layout/controls/chooseAChartingLibrary/version.js create mode 100644 src/cat/layout/controls/chooseAChartingLibrary/webchartsVersion.js create mode 100644 src/cat/layout/controls/chooseADataset.js create mode 100644 src/cat/layout/controls/chooseADataset/dataFile.js create mode 100644 src/cat/layout/controls/chooseADataset/loadADataFile.js create mode 100644 src/cat/layout/controls/customizeTheChart.js create mode 100644 src/cat/layout/controls/environment.js create mode 100644 src/cat/layout/controls/renderChart.js create mode 100644 src/cat/layout/table.js rename src/cat/{controls/initSubmit/addControlsToggle.js => layout/toggleDisplayOfControls.js} (53%) diff --git a/README.md b/README.md index c0641dc..18ab35f 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,76 @@ # CAT + +## Overview This Charting Application Tester (CAT) lets users make and adjust web graphics on the fly. -## Controls +### 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 renderer library/version and charting library/versoin will be loaded +#### 1. Choose a Charting Library +controls which charting application library/version and charting library/version will be loaded -#### Render Chart +##### 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 renderer. +3. Initializes the selected charting application library. -#### Library: -dropdown with a list of charting libraries +##### Library: +dropdown with a list of charting application libraries -1. Loads the selected version of the selected renderer 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. + 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. Loads the main .css file if the library has a .css file. -2. Updates the settings text/form. + 4. Optionally loads the main .css file if the library has a .css file. +4. Updates the settings text/form. -#### Version: -dropdown with a list of branches and releases for the selected library +##### Version: +dropdown with a list of branches and releases for the selected charting application library -1. Loads the selected version of the selected renderer 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. + 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. Loads the main .css file if the library has a .css file. -2. Updates the settings text/form. + 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 library +##### Init: +input that allows the specification of the namespace of the selected charting application library -#### . -optional input that allows the specification of the method that generates the chart +##### . +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; Webcharts is currently the only charting library supported +##### 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. Loads the selected charting library. +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 renderer library +##### Schema: +input that accepts the name of the settings schema of the selected charting application library -### 2. Choose a Dataset -controls the selected data file +#### 2. Choose a Dataset +dropdown that allows the selection of data file with which to initialize the selected charting application -#### :magnifying glass: +##### :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 library's settings schema +#### 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 +##### 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 library +##### Settings:-form +a list of inputs generated with the settings schema of the loaded charting application library -### 4. Environment -a list of the loaded stylesheets and JavaScript files +#### 4. Environment +a list of the loaded .css, .js, and stylesheets diff --git a/build/cat.js b/build/cat.js index d037cea..dbe0d08 100644 --- a/build/cat.js +++ b/build/cat.js @@ -403,41 +403,7 @@ }); } - 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() { + function toggleDisplayOfControls() { var _this = this; var styleSheet = Array.from(document.styleSheets).find(function (styleSheet) { @@ -447,9 +413,8 @@ return cssRule.selectorText === '.cat-wrap .cat-controls'; }).style.width; - //Minimize controls. - this.controls.minimize = this.wrap.append('div').classed('cat-button cat-button--minimize hidden', true).attr('title', 'Hide controls').text('<<'); - this.controls.minimize.on('click', function () { + //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) { @@ -458,13 +423,12 @@ } catch (error) {} }); _this.dataWrap.style('margin-left', 0); - _this.controls.minimize.classed('hidden', true); - _this.controls.maximize.classed('hidden', false); + _this.hideControls.classed('hidden', true); + _this.showControls.classed('hidden', false); }); - //Maximize controls. - this.controls.maximize = this.wrap.append('div').classed('cat-button cat-button--maximize hidden', true).attr('title', 'Show controls').text('>>'); - this.controls.maximize.on('click', function () { + //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) { @@ -473,8 +437,159 @@ } catch (error) {} }); _this.dataWrap.style('margin-left', controlsWidth); - _this.controls.minimize.classed('hidden', false); - _this.controls.maximize.classed('hidden', true); + _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.rendererSelect.selectAll('option').data(this.config.renderers).enter().append('option').text(function (d) { + return d.name; + }); + 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 moreOptions() { + this.controls.moreOptions = this.controls.rendererWrap.append('a').text('More Options').style('text-decoration', 'underline').style('color', 'blue').style('cursor', 'pointer'); + } + + 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); + webchartsVersion.call(this); + schema.call(this); + } + + function dataFile() { + this.controls.dataFileSelect = this.controls.dataWrap.append('select'); + this.controls.dataFileSelect.selectAll('option').data(this.config.dataFiles).enter().append('option').text(function (d) { + return d; + }); + } + + 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); + } + + var defaultSettings = { + useServer: false, + rootURL: null, + dataURL: null, + dataFiles: [], + renderers: [] + }; + + function setDefaults() { + var _this = this; + + this.config.useServer = this.config.useServer || defaultSettings.useServer; + this.config.rootURL = this.config.rootURL || defaultSettings.rootURL; + this.config.dataURL = this.config.dataURL || defaultSettings.dataURL; + this.config.dataFiles = this.config.dataFiles || defaultSettings.dataFiles; + this.config.renderers = this.config.renderers || defaultSettings.renderers; + + this.config.dataFiles = this.config.dataFiles.map(function (df) { + return typeof df === 'string' ? { label: df, path: _this.config.dataURL, user_loaded: false } : df; }); } @@ -614,11 +729,70 @@ } }; - function loadPackageJson(cat) { + function loadWebcharts(version) { + version = version || this.controls.libraryVersion.node().value; + var library = 'webcharts'; //hardcode to webcharts for now - could generalize later + + // --- load css --- // + var cssPath = version !== 'master' ? this.config.rootURL + '/Webcharts@' + version + '/css/webcharts.css' : this.config.rootURL + '/Webcharts/css/webcharts.css'; + + var link = document.createElement('link'); + link.href = cssPath; + link.type = 'text/css'; + link.rel = 'stylesheet'; + document.getElementsByTagName('head')[0].appendChild(link); + + // --- load js --- // + var rendererPath = version !== 'master' ? this.config.rootURL + '/' + library + '@' + version + '/build/webcharts.js' : this.config.rootURL + '/Webcharts/build/webcharts.js'; + + var loader = new scriptLoader(); + loader.require(rendererPath, { + async: true, + success: function success() { + //this.status.loadStatus(this.statusDiv, true, rendererPath, library, version); + }, + failure: function failure() { + //this.status.loadStatus(this.statusDiv, false, rendererPath, library, version); + } + }); + } + + function getVersions(select) { + var repo = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'https://api.github.com/repos/RhoInc/Webcharts'; + + var branches = fetch(repo + '/branches').then(function (response) { + return response.json(); + }); + var releases = fetch(repo + '/releases').then(function (response) { + return response.json(); + }); + + 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; + }); + select.selectAll('option').remove(); + select.selectAll('option').data(d3.merge(values)).enter().append('option').text(function (d) { + return d.tag_name || d.name; + }).property('selected', function (d) { + return d.name === 'master'; + }); + }).catch(function (err) { + console.log(err); + }); + } + + function loadPackageJson() { + var _this = this; + 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; + _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'); + xhr.open('GET', _this.current.url + '/package.json'); xhr.onload = function () { if (this.status === 200) { resolve(xhr.response); @@ -639,6 +813,115 @@ }); } + function loadRenderer(version, response) { + this.current.package = JSON.parse(response); + this.current.js_url = this.current.url + '/' + this.current.package.main.replace(/^\.?\/?/, ''); + 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); + } + + var loader = new scriptLoader(); + this.current.script = loader.require(this.current.js_url, { + async: true, + success: function success() { + //this.status.loadStatus( + // this.statusDiv, + // true, + // this.current.js_url, + // this.current.name, + // version || this.current.version + //); + }, + failure: function failure() { + //this.status.loadStatus( + // this.statusDiv, + // false, + // this.current.js_url, + // this.current.name, + // version || this.current.version + //); + } + }); + } + + function init$1() { + var _this = this; + + //layout + layout.call(this); + + //settings + setDefaults.call(this); + + //initialize controls + this.controls.init.call(this); + + //load charting library and charting application library + loadWebcharts.call(this, 'master'); + getVersions.call(this, this.controls.libraryVersion); + this.current = this.config.renderers[0]; + this.current.version = 'master'; + loadPackageJson.call(this).then(function (response) { + loadRenderer.call(_this, 'master', response); + getVersions.call(_this, _this.controls.versionSelect, _this.current.api_url); + }); + } + + 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(); + } + + 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); + } + + function loadData() { + 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 getCSS() { var current_css = []; d3.selectAll('link').each(function () { @@ -666,26 +949,6 @@ 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 = ""; - } - - /* 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(); @@ -736,341 +999,107 @@ jsItems.exit().remove(); } - 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 initializeChart(data) { + this.chartWrap.append('div').attr('class', 'chart'); - 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) { - 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 { - cat.current.instance = window[cat.current.main]('.cat-chart .chart', cat.current.config); - cat.status.chartCreateStatus(cat.statusDiv, cat.current.main); - } + this.status.loadStatus(this.statusDiv, true, this.dataObject.dataFilePath); - cat.current.htmlExport = createChartExport(cat); // save the source code before init - - try { - cat.current.instance.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; - } - } - cat.current.rendered = true; - } - - if (dataObject.json) render(false, dataObject.json);else if (dataObject.user_loaded) { - dataObject.json = d3.csv.parse(dataObject.csv_raw); - render(false, dataObject.json); + 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 { - var dataFilePath = dataObject.path + dataFile; - d3.csv(dataFilePath, function (error, data) { - dataObject.json = data; - render(error, dataObject.json); - }); + this.current.instance = window[this.current.main]('.cat-chart .chart', this.current.config); + this.status.chartCreateStatus(this.statusDiv, this.current.main); } - } - - 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(f => f.link == cat.current.css_url); - //var css_loaded = current_css.length > 0; - //if (!css_loaded) { - cat.current.link = document.createElement('link'); - cat.current.link.href = cat.current.css_url; - - cat.current.link.type = 'text/css'; - cat.current.link.rel = 'stylesheet'; - document.getElementsByTagName('head')[0].appendChild(cat.current.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(); - cat.current.script = 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); - //} - }); - } + //this.current.htmlExport = createChartExport(this); // save the source code before init - function loadLibrary(cat) { - var version = cat.controls.libraryVersion.node().value; - var library = 'webcharts'; //hardcode to webcharts for now - could generalize later + 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); - // --- load css --- // - var cssPath = version !== 'master' ? cat.config.rootURL + '/Webcharts@' + version + '/css/webcharts.css' : cat.config.rootURL + '/Webcharts/css/webcharts.css'; + // save to server button + if (this.config.useServer) { + this.status.saveToServer(this); + } + //showEnv(this); - 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); + //don't print any new statuses until a new chart is rendered + this.printStatus = false; } - // --- load js --- // - var rendererPath = version !== 'master' ? cat.config.rootURL + '/' + library + '@' + version + '/build/webcharts.js' : cat.config.rootURL + '/Webcharts/build/webcharts.js'; - - 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); - }, - failure: function failure() { - cat.status.loadStatus(cat.statusDiv, false, rendererPath, library, version); - } - }); - } else { - cat.status.loadStatus(cat.statusDiv, true, rendererPath, library, version); - loadRenderer(cat); - } + this.current.rendered = true; } - function addSubmitButton() { + /* + 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. + */ + + function initSubmit() { 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.controls.submitButton.on('click', function () { _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(); - - console.log(_this.previous); - 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(); - _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); - console.log(_this.current); - }); - } - - function initSubmit(cat) { - addControlsToggle.call(cat); - addSubmitButton.call(cat); - } - - function updateRenderer(select) { - var _this = this; - - this.previous = _.clone(this.current); - 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; + destroyChart.call(_this); + updateStatus.call(_this); + loadData.call(_this).then(function (json) { + initializeChart.call(_this, json); + }); }); - - //Re-initialize the chart config section - this.settings.set(this); } - function getVersions(select) { - var repo = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'https://api.github.com/repos/RhoInc/Webcharts'; + function unloadDOM() { + d3.selectAll('link').filter(function () { + return !this.href.indexOf('css/cat.css'); + }).property('disabled', true).remove(); - var branches = fetch(repo + '/branches').then(function (response) { - return response.json(); - }); - var releases = fetch(repo + '/releases').then(function (response) { - return response.json(); - }); - - 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; - }); - select.selectAll('option').remove(); - select.selectAll('option').data(d3.merge(values)).enter().append('option').text(function (d) { - return d.tag_name || d.name; - }).property('selected', function (d) { - return d.name === 'master'; - }); - }).catch(function (err) { - console.log(err); - }); + d3.selectAll('style').property('disabled', true).remove(); } - 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. + */ + + function initRendererSelect() { + unloadDOM.call(this); + //updateStatus.call(this); - //renderers - cat.controls.rendererSelect = cat.controls.rendererWrap.append('select'); - cat.controls.rendererSelect.selectAll('option').data(cat.config.renderers).enter().append('option').text(function (d) { + var cat = this; + this.controls.rendererSelect.selectAll('option').data(this.config.renderers).enter().append('option').text(function (d) { return 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'); - - //renderer version - cat.controls.rendererWrap.append('span').text('Version: '); - cat.controls.versionSelect = cat.controls.rendererWrap.append('select'); - getVersions(cat.controls.versionSelect, cat.current.api_url); - //cat.controls.versionSelect.node().value = 'master'; - //cat.controls.versionSelect.on('input', function() { - // console.log(this.value); - //}); - cat.controls.versionSelect.on('change', function () { + this.controls.versionSelect.on('change', function () { console.log(this.value); cat.current.version = this.value; 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 () { + this.controls.moreOptions.on('click', function () { d3.select(this).remove(); cat.controls.rendererWrap.selectAll('*').classed('hidden', false); }); - - //name of method that creates 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); - - //name of method that initializes chart - 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 version - cat.controls.rendererWrap.append('span').text('Webcharts Version: ').classed('hidden', true); - cat.controls.libraryVersion = cat.controls.rendererWrap.append('select').classed('hidden', true); - getVersions(cat.controls.libraryVersion); - //cat.controls.libraryVersion.node().value = 'master'; - cat.controls.rendererWrap.append('br').classed('hidden', true); - - //schema - 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); } function showDataPreview(cat) { @@ -1146,31 +1175,22 @@ } } - function initDataSelect(cat) { - cat.controls.dataWrap.append('h3').text('2. Choose a Dataset'); - cat.controls.dataFileSelect = cat.controls.dataWrap.append('select'); + function initDataSelect() { + var _this = this; - cat.controls.dataWrap.append('span').html('🔍').style('cursor', 'pointer').on('click', function () { - showDataPreview(cat); + this.controls.viewData.on('click', function () { + showDataPreview(this); }); - 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; + this.controls.dataFileSelect.selectAll('option').property('selected', function (d) { + return _this.current.defaultData === d; }); } 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 () { + 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'); cat.controls.dataFileLoadButton.attr('disabled', null); @@ -1180,7 +1200,7 @@ } }); - cat.controls.dataFileLoadButton = cat.controls.dataWrap.append('button').text('Load').attr('class', 'file-load-button').attr('disabled', true).on('click', function (d) { + this.controls.dataFileLoadButton.on('click', function (d) { //credit to https://jsfiddle.net/Ln37kqc0/ var files = cat.controls.dataFileLoad.node().files; @@ -1220,33 +1240,10 @@ }); } - 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) { + function initChartConfig() { + var cat = this; + + 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. @@ -1259,43 +1256,23 @@ 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); } - 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); + function initEnvConfig() { + showEnv(this); } - 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); + function init$2() { + 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); } function addEnterEventListener(selection, cat) { @@ -1314,31 +1291,11 @@ Define controls object. \------------------------------------------------------------------------------------------------*/ - var controls = { - init: init$1, + var controls$1 = { + init: init$2, addEnterEventListener: addEnterEventListener }; - var defaultSettings = { - useServer: false, - rootURL: null, - dataURL: null, - dataFiles: [], - renderers: [] - }; - - 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; - }); - } - function makeForm(cat, obj) { d3.select('.settingsForm form').selectAll('*').remove(); @@ -1577,7 +1534,7 @@ }); } - function loadStatus(statusDiv, passed, path, library, version) { + function loadStatus$1(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 + ')'; @@ -1593,7 +1550,7 @@ chartCreateStatus: chartCreateStatus, chartInitStatus: chartInitStatus, saveToServer: saveToServer, - loadStatus: loadStatus + loadStatus: loadStatus$1 }; function createCat() { @@ -1603,10 +1560,8 @@ var cat = { element: element, config: config, - init: init, - layout: layout, - controls: controls, - setDefaults: setDefaults, + init: init$1, + controls: controls$1, settings: settings, status: status }; diff --git a/css/cat.css b/css/cat.css index 8dd6ddf..b6ab4b9 100644 --- a/css/cat.css +++ b/css/cat.css @@ -54,9 +54,9 @@ position: relative; } .cat-wrap .cat-button { - position: absolute; - top: 3px; - left: 5px; + position: fixed; + top: 10px; + left: 12px; vertical-align: middle; background: white; cursor: pointer; 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 d522ed1..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 Dataset'); - 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 efb4b4f..0140424 100644 --- a/src/cat/controls/initRendererSelect.js +++ b/src/cat/controls/initRendererSelect.js @@ -1,87 +1,48 @@ -import updateRenderer from './initRendererSelect/updateRenderer'; -import getVersions from './initRendererSelect/getVersions'; - -export function initRendererSelect(cat) { - cat.controls.rendererWrap.append('h3').text('1. Choose a Charting Library'); - cat.controls.rendererWrap.append('span').text('Library: '); - - //renderers - cat.controls.rendererSelect = cat.controls.rendererWrap.append('select'); - cat.controls.rendererSelect +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'; + +/* + 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. +*/ + +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'); - - //renderer version - cat.controls.rendererWrap.append('span').text('Version: '); - cat.controls.versionSelect = cat.controls.rendererWrap.append('select'); - getVersions(cat.controls.versionSelect, cat.current.api_url); - //cat.controls.versionSelect.node().value = 'master'; - //cat.controls.versionSelect.on('input', function() { - // console.log(this.value); - //}); - cat.controls.versionSelect.on('change', function() { + this.controls.versionSelect.on('change', function() { console.log(this.value); cat.current.version = this.value; 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); }); - - //name of method that creates 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); - - //name of method that initializes chart - 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 version - cat.controls.rendererWrap - .append('span') - .text('Webcharts Version: ') - .classed('hidden', true); - cat.controls.libraryVersion = cat.controls.rendererWrap - .append('select') - .classed('hidden', true); - getVersions(cat.controls.libraryVersion); - //cat.controls.libraryVersion.node().value = 'master'; - cat.controls.rendererWrap.append('br').classed('hidden', true); - - //schema - 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/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/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/addSubmitButton.js b/src/cat/controls/initSubmit/addSubmitButton.js deleted file mode 100644 index 7c82d95..0000000 --- a/src/cat/controls/initSubmit/addSubmitButton.js +++ /dev/null @@ -1,67 +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(); - - console.log(this.previous); - 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(); - 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); - console.log(this.current); - }); -} 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/init.js b/src/cat/init.js index 563002c..2b182cc 100644 --- a/src/cat/init.js +++ b/src/cat/init.js @@ -1,16 +1,28 @@ -export function init() { - //layout the cat - this.wrap = d3 - .select(this.element) - .append('div') - .attr('class', 'cat-wrap'); - this.layout(this); +import layout from './layout'; +import setDefaults from './setDefaults'; +import loadWebcharts from './init/loadWebcharts'; +import getVersions from './init/getVersions'; +import loadPackageJSON from './init/loadPackageJSON'; +import loadRenderer from './init/loadRenderer'; - //initialize the settings - this.setDefaults(this); +export default function init() { + //layout + layout.call(this); - //add others here! + //settings + setDefaults.call(this); - //create the controls - this.controls.init(this); + //initialize controls + this.controls.init.call(this); + + //load charting library and charting application library + 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/init/getVersions.js b/src/cat/init/getVersions.js new file mode 100644 index 0000000..ced9b2b --- /dev/null +++ b/src/cat/init/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/init/loadPackageJSON.js b/src/cat/init/loadPackageJSON.js new file mode 100644 index 0000000..829e322 --- /dev/null +++ b/src/cat/init/loadPackageJSON.js @@ -0,0 +1,29 @@ +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 + }`; + const xhr = new XMLHttpRequest(); + xhr.open('GET', `${this.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(); + }); +} diff --git a/src/cat/init/loadRenderer.js b/src/cat/init/loadRenderer.js new file mode 100644 index 0000000..db1876a --- /dev/null +++ b/src/cat/init/loadRenderer.js @@ -0,0 +1,42 @@ +import scriptLoader from '../../util/scriptLoader'; + +export default function loadRenderer(version, response) { + this.current.package = JSON.parse(response); + this.current.js_url = `${this.current.url}/${this.current.package.main.replace( + /^\.?\/?/, + '' + )}`; + 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); + } + + 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/init/loadWebcharts.js b/src/cat/init/loadWebcharts.js new file mode 100644 index 0000000..afeb66a --- /dev/null +++ b/src/cat/init/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/init/updateSettings.js b/src/cat/init/updateSettings.js new file mode 100644 index 0000000..3b8cb13 --- /dev/null +++ b/src/cat/init/updateSettings.js @@ -0,0 +1,2 @@ +export default function updateSettings() { +} diff --git a/src/cat/layout.js b/src/cat/layout.js index 9df959a..248d463 100644 --- a/src/cat/layout.js +++ b/src/cat/layout.js @@ -1,35 +1,35 @@ -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); +import toggleDisplayOfControls from './layout/toggleDisplayOfControls'; +import controls from './layout/controls'; - /* Layout CAT Controls Divs */ - cat.controls.wrap - .append('h2') - .classed('cat-controls-header', true) - .text('Charting Application Tester 😼'); - - cat.controls.submitWrap = cat.controls.wrap +export default function layout() { + this.wrap = d3 + .select(this.element) .append('div') - .classed('control-section submit-section', true); + .attr('class', 'cat-wrap'); - cat.controls.rendererWrap = cat.controls.wrap + //Controls display toggle + this.hideControls = this.wrap .append('div') - .classed('control-section renderer-section', true); - - cat.controls.dataWrap = cat.controls.wrap + .classed('cat-button cat-button--hide-controls', true) + .attr('title', 'Hide controls') + .text('<<'); + this.showControls = this.wrap .append('div') - .classed('control-section data-section', true); + .classed('cat-button cat-button--show-controls hidden', true) + .attr('title', 'Show controls') + .text('>>'); + toggleDisplayOfControls.call(this); - cat.controls.settingsWrap = cat.controls.wrap - .append('div') - .classed('control-section settings-section', true); + //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); - cat.controls.environmentWrap = cat.controls.wrap + //Table + this.dataWrap = this.wrap .append('div') - .classed('control-section environment-section', true); + .classed('cat-data section', true) + .classed('hidden', true); } diff --git a/src/cat/layout/chart.js b/src/cat/layout/chart.js new file mode 100644 index 0000000..e69de29 diff --git a/src/cat/layout/controls.js b/src/cat/layout/controls.js new file mode 100644 index 0000000..05fbd9a --- /dev/null +++ b/src/cat/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/layout/controls/chooseAChartingLibrary.js b/src/cat/layout/controls/chooseAChartingLibrary.js new file mode 100644 index 0000000..ffc459f --- /dev/null +++ b/src/cat/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); + webchartsVersion.call(this); + schema.call(this); +} diff --git a/src/cat/layout/controls/chooseAChartingLibrary/init.js b/src/cat/layout/controls/chooseAChartingLibrary/init.js new file mode 100644 index 0000000..7f57ea1 --- /dev/null +++ b/src/cat/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/layout/controls/chooseAChartingLibrary/library.js b/src/cat/layout/controls/chooseAChartingLibrary/library.js new file mode 100644 index 0000000..9830611 --- /dev/null +++ b/src/cat/layout/controls/chooseAChartingLibrary/library.js @@ -0,0 +1,11 @@ +export default function library() { + this.controls.rendererWrap.append('span').text('Library: '); + this.controls.rendererSelect = this.controls.rendererWrap.append('select'); + this.controls.rendererSelect + .selectAll('option') + .data(this.config.renderers) + .enter() + .append('option') + .text(d => d.name); + this.controls.rendererWrap.append('br'); +} diff --git a/src/cat/layout/controls/chooseAChartingLibrary/moreOptions.js b/src/cat/layout/controls/chooseAChartingLibrary/moreOptions.js new file mode 100644 index 0000000..d27627b --- /dev/null +++ b/src/cat/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/layout/controls/chooseAChartingLibrary/schema.js b/src/cat/layout/controls/chooseAChartingLibrary/schema.js new file mode 100644 index 0000000..04f29f0 --- /dev/null +++ b/src/cat/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/layout/controls/chooseAChartingLibrary/version.js b/src/cat/layout/controls/chooseAChartingLibrary/version.js new file mode 100644 index 0000000..ae58a9e --- /dev/null +++ b/src/cat/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/layout/controls/chooseAChartingLibrary/webchartsVersion.js b/src/cat/layout/controls/chooseAChartingLibrary/webchartsVersion.js new file mode 100644 index 0000000..6cd78f1 --- /dev/null +++ b/src/cat/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/layout/controls/chooseADataset.js b/src/cat/layout/controls/chooseADataset.js new file mode 100644 index 0000000..414829f --- /dev/null +++ b/src/cat/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/layout/controls/chooseADataset/dataFile.js b/src/cat/layout/controls/chooseADataset/dataFile.js new file mode 100644 index 0000000..8333ebb --- /dev/null +++ b/src/cat/layout/controls/chooseADataset/dataFile.js @@ -0,0 +1,9 @@ +export default function dataFile() { + this.controls.dataFileSelect = this.controls.dataWrap.append('select'); + this.controls.dataFileSelect + .selectAll('option') + .data(this.config.dataFiles) + .enter() + .append('option') + .text(d => d); +} diff --git a/src/cat/layout/controls/chooseADataset/loadADataFile.js b/src/cat/layout/controls/chooseADataset/loadADataFile.js new file mode 100644 index 0000000..7c2c141 --- /dev/null +++ b/src/cat/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/layout/controls/customizeTheChart.js b/src/cat/layout/controls/customizeTheChart.js new file mode 100644 index 0000000..d9daf4b --- /dev/null +++ b/src/cat/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/layout/controls/environment.js b/src/cat/layout/controls/environment.js new file mode 100644 index 0000000..36b0a4d --- /dev/null +++ b/src/cat/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/layout/controls/renderChart.js b/src/cat/layout/controls/renderChart.js new file mode 100644 index 0000000..c15a295 --- /dev/null +++ b/src/cat/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/layout/table.js b/src/cat/layout/table.js new file mode 100644 index 0000000..e69de29 diff --git a/src/cat/controls/initSubmit/addControlsToggle.js b/src/cat/layout/toggleDisplayOfControls.js similarity index 53% rename from src/cat/controls/initSubmit/addControlsToggle.js rename to src/cat/layout/toggleDisplayOfControls.js index bab4a44..17355d7 100644 --- a/src/cat/controls/initSubmit/addControlsToggle.js +++ b/src/cat/layout/toggleDisplayOfControls.js @@ -1,4 +1,4 @@ -export default function addControlsToggle() { +export default function toggleDisplayOfControls() { const styleSheet = Array.from(document.styleSheets).find( styleSheet => styleSheet.href.indexOf('cat.css') > -1 ); @@ -6,13 +6,8 @@ export default function addControlsToggle() { cssRule => cssRule.selectorText === '.cat-wrap .cat-controls' ).style.width; - //Minimize controls. - this.controls.minimize = this.wrap - .append('div') - .classed('cat-button cat-button--minimize hidden', true) - .attr('title', 'Hide controls') - .text('<<'); - this.controls.minimize.on('click', () => { + //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) { @@ -21,17 +16,12 @@ export default function addControlsToggle() { } catch (error) {} }); this.dataWrap.style('margin-left', 0); - this.controls.minimize.classed('hidden', true); - this.controls.maximize.classed('hidden', false); + this.hideControls.classed('hidden', true); + this.showControls.classed('hidden', false); }); - //Maximize controls. - this.controls.maximize = this.wrap - .append('div') - .classed('cat-button cat-button--maximize hidden', true) - .attr('title', 'Show controls') - .text('>>'); - this.controls.maximize.on('click', () => { + //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) { @@ -40,7 +30,7 @@ export default function addControlsToggle() { } catch (error) {} }); this.dataWrap.style('margin-left', controlsWidth); - this.controls.minimize.classed('hidden', false); - this.controls.maximize.classed('hidden', true); + this.hideControls.classed('hidden', false); + this.showControls.classed('hidden', 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 b7b2e1c..d2425b8 100644 --- a/src/cat/loadRenderer.js +++ b/src/cat/loadRenderer.js @@ -1,7 +1,7 @@ import loadPackageJson from './loadRenderer/loadPackageJson'; import { getCSS } from './env/getCSS'; import { getJS } from './env/getJS'; -import { scriptLoader } from '../util/scriptLoader'; +import scriptLoader from '../util/scriptLoader'; import { renderChart } from './renderChart'; export function loadRenderer(cat) { diff --git a/src/cat/setDefaults.js b/src/cat/setDefaults.js index a23fce0..21d9535 100644 --- a/src/cat/setDefaults.js +++ b/src/cat/setDefaults.js @@ -1,15 +1,15 @@ 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; +export default function setDefaults() { + this.config.useServer = this.config.useServer || defaultSettings.useServer; + this.config.rootURL = this.config.rootURL || defaultSettings.rootURL; + this.config.dataURL = this.config.dataURL || defaultSettings.dataURL; + this.config.dataFiles = this.config.dataFiles || defaultSettings.dataFiles; + this.config.renderers = this.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 } + 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/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..d29c165 100644 --- a/src/createCat.js +++ b/src/createCat.js @@ -1,20 +1,16 @@ -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 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, + init, + controls, + settings, + status }; return cat; diff --git a/src/util/scriptLoader.js b/src/util/scriptLoader.js index 656223c..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( From c4fd7c8b41b7252eff6221598a834ed2595d8307 Mon Sep 17 00:00:00 2001 From: Spencer Childress Date: Sat, 1 Jun 2019 15:58:32 -0400 Subject: [PATCH 8/8] continue refactor - adding functionality back to controls - next up is init/schema/webcharts version --- README.md | 3 +- build/cat.js | 1268 +++++------------ index.js | 17 +- src/cat/defaultSettings.js | 15 +- src/cat/init.js | 38 +- src/cat/init/initializeControls.js | 9 + .../init/initializeControls/changeLibrary.js | 26 + .../changeLibraryVersion.js | 13 + .../init/initializeControls/renderChart.js | 21 + .../renderChart/destroyChart.js | 26 + .../renderChart/initializeChart.js | 23 + .../renderChart/loadData.js | 8 + src/cat/{ => init}/layout.js | 0 src/cat/{ => init}/layout/chart.js | 0 src/cat/{ => init}/layout/controls.js | 0 .../layout/controls/chooseAChartingLibrary.js | 4 +- .../controls/chooseAChartingLibrary/init.js | 10 +- .../chooseAChartingLibrary/library.js | 6 - .../chooseAChartingLibrary/moreOptions.js | 0 .../controls/chooseAChartingLibrary/schema.js | 6 +- .../chooseAChartingLibrary/version.js | 0 .../webchartsVersion.js | 6 +- .../layout/controls/chooseADataset.js | 0 .../controls/chooseADataset/dataFile.js | 3 + .../controls/chooseADataset/loadADataFile.js | 0 .../layout/controls/customizeTheChart.js | 0 .../{ => init}/layout/controls/environment.js | 0 .../{ => init}/layout/controls/renderChart.js | 0 src/cat/{ => init}/layout/table.js | 0 .../layout/toggleDisplayOfControls.js | 0 src/cat/init/loadChartingApplication.js | 20 + src/cat/init/loadChartingLibrary.js | 18 + src/cat/init/loadData.js | 4 + src/cat/{ => init}/setDefaults.js | 11 +- .../controls/chooseADataset/dataFile.js | 9 - src/cat/loadRenderer.js | 86 +- src/cat/{init => loadRenderer}/getVersions.js | 0 src/cat/loadRenderer/loadData.js | 0 src/cat/loadRenderer/loadLibrary.js | 32 + src/cat/loadRenderer/loadPackageJson.js | 18 +- .../{init => loadRenderer}/loadRenderer.js | 28 +- .../{init => loadRenderer}/loadWebcharts.js | 0 .../{init => loadRenderer}/updateSettings.js | 0 src/createCat.js | 14 +- src/util/defaultSettings.js | 0 src/util/getVersions.js | 18 + src/util/loadFiles.js | 34 + src/{cat/init => util}/loadPackageJSON.js | 15 +- src/util/updateFields.js | 8 + src/util/updateSelect.js | 8 + src/util/utilities.js | 15 + 51 files changed, 773 insertions(+), 1067 deletions(-) create mode 100644 src/cat/init/initializeControls.js create mode 100644 src/cat/init/initializeControls/changeLibrary.js create mode 100644 src/cat/init/initializeControls/changeLibraryVersion.js create mode 100644 src/cat/init/initializeControls/renderChart.js create mode 100644 src/cat/init/initializeControls/renderChart/destroyChart.js create mode 100644 src/cat/init/initializeControls/renderChart/initializeChart.js create mode 100644 src/cat/init/initializeControls/renderChart/loadData.js rename src/cat/{ => init}/layout.js (100%) rename src/cat/{ => init}/layout/chart.js (100%) rename src/cat/{ => init}/layout/controls.js (100%) rename src/cat/{ => init}/layout/controls/chooseAChartingLibrary.js (96%) rename src/cat/{ => init}/layout/controls/chooseAChartingLibrary/init.js (59%) rename src/cat/{ => init}/layout/controls/chooseAChartingLibrary/library.js (57%) rename src/cat/{ => init}/layout/controls/chooseAChartingLibrary/moreOptions.js (100%) rename src/cat/{ => init}/layout/controls/chooseAChartingLibrary/schema.js (56%) rename src/cat/{ => init}/layout/controls/chooseAChartingLibrary/version.js (100%) rename src/cat/{ => init}/layout/controls/chooseAChartingLibrary/webchartsVersion.js (61%) rename src/cat/{ => init}/layout/controls/chooseADataset.js (100%) create mode 100644 src/cat/init/layout/controls/chooseADataset/dataFile.js rename src/cat/{ => init}/layout/controls/chooseADataset/loadADataFile.js (100%) rename src/cat/{ => init}/layout/controls/customizeTheChart.js (100%) rename src/cat/{ => init}/layout/controls/environment.js (100%) rename src/cat/{ => init}/layout/controls/renderChart.js (100%) rename src/cat/{ => init}/layout/table.js (100%) rename src/cat/{ => init}/layout/toggleDisplayOfControls.js (100%) create mode 100644 src/cat/init/loadChartingApplication.js create mode 100644 src/cat/init/loadChartingLibrary.js create mode 100644 src/cat/init/loadData.js rename src/cat/{ => init}/setDefaults.js (58%) delete mode 100644 src/cat/layout/controls/chooseADataset/dataFile.js rename src/cat/{init => loadRenderer}/getVersions.js (100%) create mode 100644 src/cat/loadRenderer/loadData.js create mode 100644 src/cat/loadRenderer/loadLibrary.js rename src/cat/{init => loadRenderer}/loadRenderer.js (70%) rename src/cat/{init => loadRenderer}/loadWebcharts.js (100%) rename src/cat/{init => loadRenderer}/updateSettings.js (100%) create mode 100644 src/util/defaultSettings.js create mode 100644 src/util/getVersions.js create mode 100644 src/util/loadFiles.js rename src/{cat/init => util}/loadPackageJSON.js (57%) create mode 100644 src/util/updateFields.js create mode 100644 src/util/updateSelect.js create mode 100644 src/util/utilities.js diff --git a/README.md b/README.md index 18ab35f..3d5c66b 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,8 @@ dropdown with a list of charting application libraries 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. Updates the settings text/form. +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 diff --git a/build/cat.js b/build/cat.js index dbe0d08..ea14fc9 100644 --- a/build/cat.js +++ b/build/cat.js @@ -403,196 +403,6 @@ }); } - 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.rendererSelect.selectAll('option').data(this.config.renderers).enter().append('option').text(function (d) { - return d.name; - }); - 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 moreOptions() { - this.controls.moreOptions = this.controls.rendererWrap.append('a').text('More Options').style('text-decoration', 'underline').style('color', 'blue').style('cursor', 'pointer'); - } - - 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); - webchartsVersion.call(this); - schema.call(this); - } - - function dataFile() { - this.controls.dataFileSelect = this.controls.dataWrap.append('select'); - this.controls.dataFileSelect.selectAll('option').data(this.config.dataFiles).enter().append('option').text(function (d) { - return d; - }); - } - - 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); - } - - var defaultSettings = { - useServer: false, - rootURL: null, - dataURL: null, - dataFiles: [], - renderers: [] - }; - - function setDefaults() { - var _this = this; - - this.config.useServer = this.config.useServer || defaultSettings.useServer; - this.config.rootURL = this.config.rootURL || defaultSettings.rootURL; - this.config.dataURL = this.config.dataURL || defaultSettings.dataURL; - this.config.dataFiles = this.config.dataFiles || defaultSettings.dataFiles; - this.config.renderers = this.config.renderers || defaultSettings.renderers; - - this.config.dataFiles = this.config.dataFiles.map(function (df) { - return typeof df === 'string' ? { label: df, path: _this.config.dataURL, user_loaded: false } : df; - }); - } - var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { @@ -637,6 +447,62 @@ }; }(); + 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() {} @@ -729,160 +595,296 @@ } }; - function loadWebcharts(version) { - version = version || this.controls.libraryVersion.node().value; - var library = 'webcharts'; //hardcode to webcharts for now - could generalize later - - // --- load css --- // - var cssPath = version !== 'master' ? this.config.rootURL + '/Webcharts@' + version + '/css/webcharts.css' : this.config.rootURL + '/Webcharts/css/webcharts.css'; - - var link = document.createElement('link'); - link.href = cssPath; - link.type = 'text/css'; - link.rel = 'stylesheet'; - document.getElementsByTagName('head')[0].appendChild(link); + 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 --- // - var rendererPath = version !== 'master' ? this.config.rootURL + '/' + library + '@' + version + '/build/webcharts.js' : this.config.rootURL + '/Webcharts/build/webcharts.js'; + //Load .js file + var jsURL = version === 'master' ? cdnURL + '/' + pkg.main.replace(/^\.?\/?/, '') : cdnURL + '@' + version + '/' + pkg.main.replace(/^\.?\/?/, ''); var loader = new scriptLoader(); - loader.require(rendererPath, { + var script = loader.require(jsURL, { async: true, success: function success() { - //this.status.loadStatus(this.statusDiv, true, rendererPath, library, version); + console.log('Loaded ' + jsURL + '.'); }, failure: function failure() { - //this.status.loadStatus(this.statusDiv, false, rendererPath, library, version); + console.warn('Failed to load ' + jsURL + '.'); } }); } - function getVersions(select) { - var repo = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'https://api.github.com/repos/RhoInc/Webcharts'; + function updateFields(version) { + var _this = this; - var branches = fetch(repo + '/branches').then(function (response) { - return response.json(); - }); - var releases = fetch(repo + '/releases').then(function (response) { - return response.json(); + 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; }); + } - Promise.all([branches, releases]).then(function (values) { - var _values = slicedToArray(values, 2), - branches = _values[0], - releases = _values[1]; + var utilities = { + getVersions: getVersions, + updateSelect: updateSelect, + loadPackageJSON: loadPackageJSON, + loadFiles: loadFiles, + scriptLoader: scriptLoader, + updateFields: updateFields + }; - branches.sort(function (a, b) { - return 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(function (d) { - return d.tag_name || d.name; - }).property('selected', function (d) { - return d.name === 'master'; - }); - }).catch(function (err) { - console.log(err); + 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 loadPackageJson() { + function toggleDisplayOfControls() { var _this = this; - return new Promise(function (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', _this.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(); + 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 loadRenderer(version, response) { - this.current.package = JSON.parse(response); - this.current.js_url = this.current.url + '/' + this.current.package.main.replace(/^\.?\/?/, ''); - this.current.css_url = this.current.css ? this.current.url + '/' + this.current.css : null; + 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'); + } - if (this.current.css) { - this.current.link = document.createElement('link'); - this.current.link.href = this.current.css_url; + function library() { + this.controls.rendererWrap.append('span').text('Library: '); + this.controls.rendererSelect = this.controls.rendererWrap.append('select'); + this.controls.rendererWrap.append('br'); + } - this.current.link.type = 'text/css'; - this.current.link.rel = 'stylesheet'; - document.getElementsByTagName('head')[0].appendChild(this.current.link); - } + function version() { + this.controls.rendererWrap.append('span').text('Version: '); + this.controls.versionSelect = this.controls.rendererWrap.append('select'); + this.controls.rendererWrap.append('br'); + } - var loader = new scriptLoader(); - this.current.script = loader.require(this.current.js_url, { - async: true, - success: function success() { - //this.status.loadStatus( - // this.statusDiv, - // true, - // this.current.js_url, - // this.current.name, - // version || this.current.version - //); - }, - failure: function failure() { - //this.status.loadStatus( - // this.statusDiv, - // false, - // this.current.js_url, - // this.current.name, - // version || this.current.version - //); - } - }); + 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 init$1() { + 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; - //layout - layout.call(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); + }); + } - //settings - setDefaults.call(this); + function loadChartingApplication() { + var _this = this; - //initialize controls - this.controls.init.call(this); - - //load charting library and charting application library - loadWebcharts.call(this, 'master'); - getVersions.call(this, this.controls.libraryVersion); - this.current = this.config.renderers[0]; - this.current.version = 'master'; - loadPackageJson.call(this).then(function (response) { - loadRenderer.call(_this, 'master', response); - getVersions.call(_this, _this.controls.versionSelect, _this.current.api_url); + 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.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(); + if (this.chartingApplicationInstance) { + if (this.chartingApplicationInstance && this.chartingApplicationInstance.destroy) { + if (this.chartingApplicationInstance && this.chartingApplicationInstance.destroy) this.chartingApplicationInstance.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 @@ -903,13 +905,7 @@ this.chartWrap.selectAll('*').remove(); } - 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); - } - - function loadData() { + function loadData$1() { var dataFile = this.controls.dataFileSelect.node().value; this.dataObject = this.config.dataFiles.find(function (f) { return f.label == dataFile; @@ -922,636 +918,109 @@ }); } - 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; - } - - 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 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(); - } - 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); + //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.current.instance = window[this.current.main]('.cat-chart .chart', this.current.config); - this.status.chartCreateStatus(this.statusDiv, this.current.main); + this.chartingApplicationInstance = window[this.chartingApplication.main]('.cat-chart .chart', this.chartingApplication.config || {}); } - //this.current.htmlExport = createChartExport(this); // save the source code before init - + //Pass data to charting application. try { - this.current.instance.init(data); + this.chartingApplicationInstance.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; + console.warn(err); } - - this.current.rendered = true; } /* 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. + 2. Loads the selected data file. + 3. Initializes the selected charting application library. */ - function initSubmit() { + 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); - updateStatus.call(_this); - loadData.call(_this).then(function (json) { + loadData$1.call(_this).then(function (json) { initializeChart.call(_this, json); }); }); } - 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(); - } - - /* - 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. - */ - - function initRendererSelect() { - unloadDOM.call(this); - //updateStatus.call(this); - + function changeLibrary() { var cat = this; - this.controls.rendererSelect.selectAll('option').data(this.config.renderers).enter().append('option').text(function (d) { - return d.name; - }); - this.controls.rendererSelect.on('change', function () { - updateRenderer.call(cat, this); - getVersions(cat.controls.versionSelect, cat.current.api_url); - }); - this.controls.versionSelect.on('change', function () { - console.log(this.value); - cat.current.version = this.value; - cat.settings.set(cat); - }); - this.controls.moreOptions.on('click', function () { - d3.select(this).remove(); - cat.controls.rendererWrap.selectAll('*').classed('hidden', false); - }); - 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); - } - - 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) { - 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) { - dataObject.data = raw; - handsOnTable(dataObject.data); - //cat.dataPreview.init(raw); + 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; }); - } - - function handsOnTable(data) { - var colHeaders = Object.keys(data[0]); - var table = new Handsontable(cat.dataWrap.select('.dataPreview').node(), { - data: data.map(function (d) { - return Object.keys(d).map(function (key) { - return d[key]; - }); - }), - colHeaders: colHeaders, - columns: colHeaders.map(function (_) { - return { type: 'text' }; - }), - rowHeaders: true, - dropdownMenu: true, - filters: true, - afterChange: function afterChange(changes) { - dataObject.json = this.getData().map(function (d) { - return d.reduce(function (acc, cur, i) { - acc[colHeaders[i]] = cur; - return acc; - }, {}); - }); - }, - afterFilter: function afterFilter(changes) { - dataObject.json = this.getData().map(function (d) { - return d.reduce(function (acc, cur, i) { - acc[colHeaders[i]] = cur; - return acc; - }, {}); - }); - }, - licenseKey: 'non-commercial-and-evaluation' + 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 initDataSelect() { - var _this = this; - - this.controls.viewData.on('click', function () { - showDataPreview(this); - }); - - this.controls.dataFileSelect.selectAll('option').property('selected', function (d) { - return _this.current.defaultData === d; - }); - } - - function initFileLoad() { - var cat = this; - - 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'); - 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); - } - }); - - this.controls.dataFileLoadButton.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)); }); } - function initChartConfig() { + function changeLibraryVersion() { var cat = this; - 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. - 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); - } - }); - - this.settings.set(this); - } - - function initEnvConfig() { - showEnv(this); - } - - function init$2() { - 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); - } - - 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(); + 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); }); }); } - /*------------------------------------------------------------------------------------------------\ - Define controls object. - \------------------------------------------------------------------------------------------------*/ - - var controls$1 = { - init: init$2, - addEnterEventListener: addEnterEventListener - }; - - 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); + function initializeControls() { + renderChart$1.call(this); + changeLibrary.call(this); + changeLibraryVersion.call(this); } - 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." - }]; - - var myStatus = statusOptions.filter(function (d) { - return d.key == statusVal; - })[0]; - - cat.controls.settingsStatus.html(myStatus.symbol).style('color', myStatus.color).attr('title', myStatus.details); - } - - 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; - } - - 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'); - - cat.controls.settingsTypeForm.property('checked', cat.current.settingsView == 'form').property('disabled', !cat.current.hasValidSchema); - - // Show/Hide sections - cat.controls.settingsInput.classed('hidden', cat.current.settingsView != 'text'); - cat.controls.settingsForm.classed('hidden', cat.current.settingsView != 'form'); - - //update the text or make the schema - cat.controls.settingsInput.node().value = JSON5.stringify(cat.current.config, null, 4); - - 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; - } - - // 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); - } - } - - 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); - } - } - - /*------------------------------------------------------------------------------------------------\ - 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); - } - } - - 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); - }); + function init$1() { + //settings + setDefaults.call(this); - 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) - }; + //layout + layout.call(this); - //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.'); - }); - }); - } + //renderers and data files + loadData.call(this); - function loadStatus$1(statusDiv, passed, path, library, version) { - var message = passed ? 'Successfully loaded ' + path : 'Failed to load ' + path; + //load charting library + loadChartingLibrary.call(this); - if (library != undefined & version != undefined) message = message + ' (Library: ' + library + ', Version: ' + version + ')'; + //load charting application + loadChartingApplication.call(this); - statusDiv.append('div').html(message).classed('error', !passed); + //initialize controls + initializeControls.call(this); } - /*------------------------------------------------------------------------------------------------\ - Define controls object. - \------------------------------------------------------------------------------------------------*/ - - var status = { - chartCreateStatus: chartCreateStatus, - chartInitStatus: chartInitStatus, - saveToServer: saveToServer, - loadStatus: loadStatus$1 - }; + //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'; @@ -1560,10 +1029,11 @@ var cat = { element: element, config: config, + utilities: utilities, init: init$1, - controls: controls$1, - settings: settings, - status: status + controls: {} + //settings, + //status }; return cat; diff --git a/index.js b/index.js index 017ef39..692a193 100644 --- a/index.js +++ b/index.js @@ -1,8 +1,5 @@ const myCatConfig = { useServer: false, - rootURL: 'https://cdn.jsdelivr.net/gh/RhoInc', - dataURL: 'https://raw.githubusercontent.com/RhoInc/data-library/master/data/', - repoURL: 'https://cdn.jsdelivr.net/gh/RhoInc/graphics/data', repoURL: '../graphics/data', renderers: [ { @@ -138,13 +135,13 @@ const myCatConfig = { }; //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'; - }); +//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'; +// }); //Map data file objects to a path relative to myCatConfig.dataURL. myCatConfig.dataFiles = dataFiles 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 2b182cc..0e7bc77 100644 --- a/src/cat/init.js +++ b/src/cat/init.js @@ -1,28 +1,26 @@ -import layout from './layout'; -import setDefaults from './setDefaults'; -import loadWebcharts from './init/loadWebcharts'; -import getVersions from './init/getVersions'; -import loadPackageJSON from './init/loadPackageJSON'; -import loadRenderer from './init/loadRenderer'; +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'; export default function init() { + //settings + setDefaults.call(this); + //layout layout.call(this); - //settings - setDefaults.call(this); + //renderers and data files + loadData.call(this); - //initialize controls - this.controls.init.call(this); + //load charting library + loadChartingLibrary.call(this); - //load charting library and charting application library - 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); - }); + //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/layout.js b/src/cat/init/layout.js similarity index 100% rename from src/cat/layout.js rename to src/cat/init/layout.js diff --git a/src/cat/layout/chart.js b/src/cat/init/layout/chart.js similarity index 100% rename from src/cat/layout/chart.js rename to src/cat/init/layout/chart.js diff --git a/src/cat/layout/controls.js b/src/cat/init/layout/controls.js similarity index 100% rename from src/cat/layout/controls.js rename to src/cat/init/layout/controls.js diff --git a/src/cat/layout/controls/chooseAChartingLibrary.js b/src/cat/init/layout/controls/chooseAChartingLibrary.js similarity index 96% rename from src/cat/layout/controls/chooseAChartingLibrary.js rename to src/cat/init/layout/controls/chooseAChartingLibrary.js index ffc459f..0f42d11 100644 --- a/src/cat/layout/controls/chooseAChartingLibrary.js +++ b/src/cat/init/layout/controls/chooseAChartingLibrary.js @@ -12,8 +12,8 @@ export default function chooseAChartingLibrary() { this.controls.rendererWrap.append('h3').text('1. Choose a Charting Library'); library.call(this); version.call(this); - moreOptions.call(this); + //moreOptions.call(this); init.call(this); - webchartsVersion.call(this); schema.call(this); + webchartsVersion.call(this); } diff --git a/src/cat/layout/controls/chooseAChartingLibrary/init.js b/src/cat/init/layout/controls/chooseAChartingLibrary/init.js similarity index 59% rename from src/cat/layout/controls/chooseAChartingLibrary/init.js rename to src/cat/init/layout/controls/chooseAChartingLibrary/init.js index 7f57ea1..b595f6e 100644 --- a/src/cat/layout/controls/chooseAChartingLibrary/init.js +++ b/src/cat/init/layout/controls/chooseAChartingLibrary/init.js @@ -2,12 +2,12 @@ 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); + //.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); + //.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/layout/controls/chooseAChartingLibrary/library.js b/src/cat/init/layout/controls/chooseAChartingLibrary/library.js similarity index 57% rename from src/cat/layout/controls/chooseAChartingLibrary/library.js rename to src/cat/init/layout/controls/chooseAChartingLibrary/library.js index 9830611..f1b8f7e 100644 --- a/src/cat/layout/controls/chooseAChartingLibrary/library.js +++ b/src/cat/init/layout/controls/chooseAChartingLibrary/library.js @@ -1,11 +1,5 @@ export default function library() { this.controls.rendererWrap.append('span').text('Library: '); this.controls.rendererSelect = this.controls.rendererWrap.append('select'); - this.controls.rendererSelect - .selectAll('option') - .data(this.config.renderers) - .enter() - .append('option') - .text(d => d.name); this.controls.rendererWrap.append('br'); } diff --git a/src/cat/layout/controls/chooseAChartingLibrary/moreOptions.js b/src/cat/init/layout/controls/chooseAChartingLibrary/moreOptions.js similarity index 100% rename from src/cat/layout/controls/chooseAChartingLibrary/moreOptions.js rename to src/cat/init/layout/controls/chooseAChartingLibrary/moreOptions.js diff --git a/src/cat/layout/controls/chooseAChartingLibrary/schema.js b/src/cat/init/layout/controls/chooseAChartingLibrary/schema.js similarity index 56% rename from src/cat/layout/controls/chooseAChartingLibrary/schema.js rename to src/cat/init/layout/controls/chooseAChartingLibrary/schema.js index 04f29f0..90852ae 100644 --- a/src/cat/layout/controls/chooseAChartingLibrary/schema.js +++ b/src/cat/init/layout/controls/chooseAChartingLibrary/schema.js @@ -2,7 +2,7 @@ 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); + //.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/layout/controls/chooseAChartingLibrary/version.js b/src/cat/init/layout/controls/chooseAChartingLibrary/version.js similarity index 100% rename from src/cat/layout/controls/chooseAChartingLibrary/version.js rename to src/cat/init/layout/controls/chooseAChartingLibrary/version.js diff --git a/src/cat/layout/controls/chooseAChartingLibrary/webchartsVersion.js b/src/cat/init/layout/controls/chooseAChartingLibrary/webchartsVersion.js similarity index 61% rename from src/cat/layout/controls/chooseAChartingLibrary/webchartsVersion.js rename to src/cat/init/layout/controls/chooseAChartingLibrary/webchartsVersion.js index 6cd78f1..b24f218 100644 --- a/src/cat/layout/controls/chooseAChartingLibrary/webchartsVersion.js +++ b/src/cat/init/layout/controls/chooseAChartingLibrary/webchartsVersion.js @@ -2,9 +2,9 @@ export default function webchartsVersion() { this.controls.rendererWrap .append('span') .text('Webcharts Version: ') - .classed('hidden', true); + //.classed('hidden', true); this.controls.libraryVersion = this.controls.rendererWrap .append('select') - .classed('hidden', true); - this.controls.rendererWrap.append('br').classed('hidden', true); + //.classed('hidden', true); + this.controls.rendererWrap.append('br')//.classed('hidden', true); } diff --git a/src/cat/layout/controls/chooseADataset.js b/src/cat/init/layout/controls/chooseADataset.js similarity index 100% rename from src/cat/layout/controls/chooseADataset.js rename to src/cat/init/layout/controls/chooseADataset.js 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/layout/controls/chooseADataset/loadADataFile.js b/src/cat/init/layout/controls/chooseADataset/loadADataFile.js similarity index 100% rename from src/cat/layout/controls/chooseADataset/loadADataFile.js rename to src/cat/init/layout/controls/chooseADataset/loadADataFile.js diff --git a/src/cat/layout/controls/customizeTheChart.js b/src/cat/init/layout/controls/customizeTheChart.js similarity index 100% rename from src/cat/layout/controls/customizeTheChart.js rename to src/cat/init/layout/controls/customizeTheChart.js diff --git a/src/cat/layout/controls/environment.js b/src/cat/init/layout/controls/environment.js similarity index 100% rename from src/cat/layout/controls/environment.js rename to src/cat/init/layout/controls/environment.js diff --git a/src/cat/layout/controls/renderChart.js b/src/cat/init/layout/controls/renderChart.js similarity index 100% rename from src/cat/layout/controls/renderChart.js rename to src/cat/init/layout/controls/renderChart.js diff --git a/src/cat/layout/table.js b/src/cat/init/layout/table.js similarity index 100% rename from src/cat/layout/table.js rename to src/cat/init/layout/table.js diff --git a/src/cat/layout/toggleDisplayOfControls.js b/src/cat/init/layout/toggleDisplayOfControls.js similarity index 100% rename from src/cat/layout/toggleDisplayOfControls.js rename to src/cat/init/layout/toggleDisplayOfControls.js 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/setDefaults.js b/src/cat/init/setDefaults.js similarity index 58% rename from src/cat/setDefaults.js rename to src/cat/init/setDefaults.js index 21d9535..3d750f7 100644 --- a/src/cat/setDefaults.js +++ b/src/cat/init/setDefaults.js @@ -1,11 +1,18 @@ -import defaultSettings from './defaultSettings'; +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.dataFiles = this.config.dataFiles || defaultSettings.dataFiles; + 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' diff --git a/src/cat/layout/controls/chooseADataset/dataFile.js b/src/cat/layout/controls/chooseADataset/dataFile.js deleted file mode 100644 index 8333ebb..0000000 --- a/src/cat/layout/controls/chooseADataset/dataFile.js +++ /dev/null @@ -1,9 +0,0 @@ -export default function dataFile() { - this.controls.dataFileSelect = this.controls.dataWrap.append('select'); - this.controls.dataFileSelect - .selectAll('option') - .data(this.config.dataFiles) - .enter() - .append('option') - .text(d => d); -} diff --git a/src/cat/loadRenderer.js b/src/cat/loadRenderer.js index d2425b8..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) { - cat.current.link = document.createElement('link'); - cat.current.link.href = cat.current.css_url; - - cat.current.link.type = 'text/css'; - cat.current.link.rel = 'stylesheet'; - document.getElementsByTagName('head')[0].appendChild(cat.current.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(); - cat.current.script = 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 - ); - } +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); }); - //} else { - // cat.status.loadStatus( - // cat.statusDiv, - // true, - // cat.current.js_url, - // cat.current.name, - // cat.current.version - // ); - // renderChart(cat); - //} - }); } diff --git a/src/cat/init/getVersions.js b/src/cat/loadRenderer/getVersions.js similarity index 100% rename from src/cat/init/getVersions.js rename to src/cat/loadRenderer/getVersions.js 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/init/loadRenderer.js b/src/cat/loadRenderer/loadRenderer.js similarity index 70% rename from src/cat/init/loadRenderer.js rename to src/cat/loadRenderer/loadRenderer.js index db1876a..14d0d25 100644 --- a/src/cat/init/loadRenderer.js +++ b/src/cat/loadRenderer/loadRenderer.js @@ -1,13 +1,26 @@ import scriptLoader from '../../util/scriptLoader'; export default function loadRenderer(version, response) { + //Attach package.json. this.current.package = JSON.parse(response); - this.current.js_url = `${this.current.url}/${this.current.package.main.replace( - /^\.?\/?/, - '' - )}`; - this.current.css_url = this.current.css ? `${this.current.url}/${this.current.css}` : null; + //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; @@ -17,6 +30,11 @@ export default function loadRenderer(version, response) { 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, diff --git a/src/cat/init/loadWebcharts.js b/src/cat/loadRenderer/loadWebcharts.js similarity index 100% rename from src/cat/init/loadWebcharts.js rename to src/cat/loadRenderer/loadWebcharts.js diff --git a/src/cat/init/updateSettings.js b/src/cat/loadRenderer/updateSettings.js similarity index 100% rename from src/cat/init/updateSettings.js rename to src/cat/loadRenderer/updateSettings.js diff --git a/src/createCat.js b/src/createCat.js index d29c165..883c229 100644 --- a/src/createCat.js +++ b/src/createCat.js @@ -1,16 +1,18 @@ +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'; +//import controls from './cat/controls'; +//import settings from './cat/settings'; +//import status from './cat/status'; export function createCat(element = 'body', config) { const cat = { element, config, + utilities, init, - controls, - settings, - status + 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/cat/init/loadPackageJSON.js b/src/util/loadPackageJSON.js similarity index 57% rename from src/cat/init/loadPackageJSON.js rename to src/util/loadPackageJSON.js index 829e322..444771c 100644 --- a/src/cat/init/loadPackageJSON.js +++ b/src/util/loadPackageJSON.js @@ -1,13 +1,12 @@ -export default function loadPackageJson() { +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) => { - 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 - }`; const xhr = new XMLHttpRequest(); - xhr.open('GET', `${this.current.url}/package.json`); + xhr.open('GET', pkgURL); xhr.onload = function() { if (this.status === 200) { resolve(xhr.response); 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, +}