From ad4fc2f418d963b0444cfcdc02be31e7bcf88f8d Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Fri, 13 Feb 2015 19:03:56 +0300 Subject: [PATCH 01/93] GPL draft concept --- examples/gpl.html | 314 ++++++++++++++++++++++++++++++++++++++++++ src/charts/tau.gpl.js | 159 +++++++++++++++++++++ src/scales-factory.js | 235 +++++++++++++++++++++++++++++++ 3 files changed, 708 insertions(+) create mode 100644 examples/gpl.html create mode 100644 src/charts/tau.gpl.js create mode 100644 src/scales-factory.js diff --git a/examples/gpl.html b/examples/gpl.html new file mode 100644 index 000000000..0c749b81a --- /dev/null +++ b/examples/gpl.html @@ -0,0 +1,314 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + diff --git a/src/charts/tau.gpl.js b/src/charts/tau.gpl.js new file mode 100644 index 000000000..786fb0fa8 --- /dev/null +++ b/src/charts/tau.gpl.js @@ -0,0 +1,159 @@ +import {DSLReader} from '../dsl-reader'; +import {Emitter} from '../event'; +import {SpecEngineFactory} from '../spec-engine-factory'; +import {LayoutEngineFactory} from '../layout-engine-factory'; +import {utils} from '../utils/utils'; +import {utilsDom} from '../utils/utils-dom'; +import {CSS_PREFIX} from '../const'; +import {UnitDomainMixin} from '../unit-domain-mixin'; +import {UnitsRegistry} from '../units-registry'; +import {DataProcessor} from '../data-processor'; +import {getLayout} from '../utils/layuot-template'; + +import {ScalesFactory} from '../unit-domain-mixin'; + + + +var FramesAlgebra = { + + 'cross': function(scaleX, scaleY) { + + var dimX = scaleX.dim; + var dimY = scaleY.dim; + + var domainX = scaleX.domain(); + var domainY = scaleY.domain(); + + var domX = domainX.length === 0 ? [null] : domainX; + var domY = domainY.length === 0 ? [null] : domainY; + + var convert = (v) => (v instanceof Date) ? v.getTime() : v; + + return _(domY).reduce( + (memo, rowVal) => { + + return memo.concat(_(domX).map((colVal) => { + + var r = {}; + + if (dimX) { + r[dimX] = convert(colVal); + } + + if (dimY) { + r[dimY] = convert(rowVal); + } + + return r; + })); + + }, + []); + } +}; + + + +export class GPL extends Emitter { + + constructor(config) { + + super(); + + // config.units + + // config.sources + // config.sources.* + // config.sources.*.dims + // config.sources.*.data + + // config.scales + + // config.trans + + // config.unit + + this._svg = null; + this._filtersStore = { + filters: {}, + tick: 0 + }; + this._layout = getLayout(); + + this.initialize(config); + } + + initialize(config) { + + this.unitHub = UnitsRegistry; + + this.sources = config.sources; + + var scalesCreator = new ScalesFactory(config.sources); + this.scales = _.keys(config.scales).reduce( + (memo, key) => { + memo[key] = scalesCreator.create(config.scales[key]); + return memo; + }, + {}); + + this.trans = config.trans; + + // expand units structure + this.root = expandUnitsStructure(config.unit); + } + + expandUnitsStructure(rootUnit) { + + var buildRecursively = (root, parentFramePipe) => { + + // TODO: detached_cross - to avoid parent frame inheritance + var expr = this.parseExpression(root.expr); + + root.frames = expr.exec().map((tuple) => { + var pipe = parentFramePipe.concat([{where: tuple}]); + return { + key: tuple, + source: expr.source, + pipe: pipe, + unit: root.unit.map((unit) => buildRecursively(utils.clone(unit), pipe)) + }; + }); + + return root; + }; + + return buildRecursively(rootUnit, []); + } + + drawUnitsStructure() { + + var drawRecursively = (root) => { + + var UnitClass = this.unitHub.get(root.type); + + var unitNode = new UnitClass(); + + root.frames.map((frame) => { + unitNode.drawFrame(frame); + drawRecursively(frame.unit); + }); + + return root; + }; + + return drawRecursively(this.root); + } + + parseExpression(sExpression) { + var funcName = sExpression[0]; + var funcArgs = sExpression.slice(1).map((scaleName) => this.scales[scaleName]); + + return { + source: funcArgs[0].source, + func: FramesAlgebra[funcName], + args: funcArgs, + exec: () => FramesAlgebra[funcName](...funcArgs) + }; + } +} \ No newline at end of file diff --git a/src/scales-factory.js b/src/scales-factory.js new file mode 100644 index 000000000..d4d530e56 --- /dev/null +++ b/src/scales-factory.js @@ -0,0 +1,235 @@ +import {UnitDomainPeriodGenerator} from './unit-domain-period-generator'; +import {utils} from './utils/utils'; +/* jshint ignore:start */ +import * as _ from 'underscore'; +import * as d3 from 'd3'; +/* jshint ignore:end */ + +var scalesStrategies = { + + 'color': (vars, props) => { + + var varSet = vars; + + var brewer = props.brewer; + + var defaultColorClass = _.constant('color-default'); + + var defaultRangeColor = _.times(20, (i) => 'color20-' + (1 + i)); + + var buildArrayGetClass = (domain, brewer) => { + if (domain.length === 0 || (domain.length === 1 && domain[0] === null)) { + return defaultColorClass; + } + else { + var fullDomain = domain.map((x) => String(x).toString()); + return d3.scale.ordinal().range(brewer).domain(fullDomain); + } + }; + + var buildObjectGetClass = (brewer, defaultGetClass) => { + var domain = _.keys(brewer); + var range = _.values(brewer); + var calculateClass = d3.scale.ordinal().range(range).domain(domain); + return (d) => brewer.hasOwnProperty(d) ? calculateClass(d) : defaultGetClass(d); + }; + + var wrapString = (f) => (d) => f(String(d).toString()); + + var func; + if (!brewer) { + func = wrapString(buildArrayGetClass(varSet, defaultRangeColor)); + } + else if (_.isArray(brewer)) { + func = wrapString(buildArrayGetClass(varSet, brewer)); + } + else if (_.isFunction(brewer)) { + func = (d) => brewer(d, wrapString(buildArrayGetClass(varSet, defaultRangeColor))); + } + else if (_.isObject(brewer)) { + func = buildObjectGetClass(brewer, defaultColorClass); + } + else { + throw new Error('This brewer is not supported'); + } + + var wrap = func; + + return { + init: function() { + + wrap.legend = (v) => { + + // var value = varSet.extract(v); + var value = v; + var label = (props.tickLabel) ? ((v || {})[props.tickLabel]) : (value); + var color = func(value); + + return {value, color, label}; + }; + + return wrap; + } + }; + }, + + 'size': (varSet, props) => { + + var minSize = props.min; + var maxSize = props.max; + var midSize = props.mid; + + var f = (x) => Math.sqrt(x); + + var values = _.filter(varSet, _.isFinite); + if (values.length === 0) { + return (x) => midSize; + } + + var k = 1; + var xMin = 0; + + var min = Math.min.apply(null, values); + var max = Math.max.apply(null, values); + + var len = f(Math.max.apply( + null, + [ + Math.abs(min), + Math.abs(max), + max - min + ])); + + xMin = (min < 0) ? min : 0; + k = (len === 0) ? 1 : ((maxSize - minSize) / len); + + var func = (x) => { + + var numX = (x !== null) ? parseFloat(x) : 0; + + if (!_.isFinite(numX)) { + return maxSize; + } + + var posX = (numX - xMin); // translate to positive x domain + + return (minSize + (f(posX) * k)); + }; + + return { + init: function (interval) { + return func; + } + }; + }, + + 'ordinal': (varSet, props) => { + var d3Domain = d3.scale.ordinal().domain(varSet); + return { + init: function(interval) { + return d3Domain.rangePoints(interval, 1); + } + }; + }, + + 'linear': (vars, props) => { + + var domain = (props.autoScale) ? utils.autoScale(vars) : d3.extent(vars); + + var min = _.isNumber(props.min) ? props.min : domain[0]; + var max = _.isNumber(props.max) ? props.max : domain[1]; + + var varSet = [ + Math.min(min, domain[0]), + Math.max(max, domain[1]) + ]; + + var d3Domain = d3.scale.linear().domain(varSet); + + return { + init: function(interval) { + return d3Domain.rangeRound(interval, 1); + } + }; + }, + + 'period': (vars, props) => { + + // extract: ((x) => UnitDomainPeriodGenerator.get(xOptions.period).cast(new Date(x))) + + var domain = d3.extent(vars); + var min = (_.isNull(props.min) || _.isUndefined(props.min)) ? domain[0] : new Date(props.min).getTime(); + var max = (_.isNull(props.max) || _.isUndefined(props.max)) ? domain[1] : new Date(props.max).getTime(); + + var range = [ + new Date(Math.min(min, domain[0])), + new Date(Math.max(max, domain[1])) + ]; + + var varSet = UnitDomainPeriodGenerator.generate(range[0], range[1], props.period); + + var d3Domain = d3.scale.ordinal().domain(varSet); + + return { + init: function(interval) { + return d3Domain.rangePoints(interval, 1); + } + }; + }, + + 'time': (vars, props) => { + + var domain = d3.extent(vars); + var min = (_.isNull(props.min) || _.isUndefined(props.min)) ? domain[0] : new Date(props.min).getTime(); + var max = (_.isNull(props.max) || _.isUndefined(props.max)) ? domain[1] : new Date(props.max).getTime(); + + var varSet = [ + new Date(Math.min(min, domain[0])), + new Date(Math.max(max, domain[1])) + ]; + + var d3Domain = d3.time.scale().domain(varSet); + + return { + init: function(interval) { + return d3Domain.range(interval); + } + }; + } +}; + +var map_value = (dimType) => { + return (dimType === 'date') ? + ((v) => (new Date(v)).getTime()) : + ((v) => v); +}; + +var where = (data, meta, whereFilter) => { + + var predicates = _(whereFilter).map((v, k) => { + var xMap = map_value(meta[k].type); + return (row) => xMap(row[k]) === v; + }); + + return _(data).filter((row) => _.every(predicates, ((p) => p(row)))); +}; + +export class ScalesFactory { + + constructor(sources) { + this.sources = sources; + } + + create(scaleConfig) { + + var dim = scaleConfig.dim; + var src = scaleConfig.source; + + var meta = this.sources[src].dims[dim]; + var data = this.sources[src].data; + + var vars = _(data).chain().pluck(dim).uniq(map_value(meta.type)).value(); + + return scalesStrategies[scaleConfig.type](vars, scaleConfig); + } +} \ No newline at end of file From 30df79190a1f2f7c96bbd47034660624bb9adb79 Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Mon, 16 Feb 2015 20:50:58 +0300 Subject: [PATCH 02/93] Sampling practical solution --- examples/gpl.html | 530 ++++++++++++++++--------------- src/charts/tau.gpl.js | 122 +++++-- src/elements/coords.cartesian.js | 75 +++++ src/elements/element.point.js | 46 +++ src/scales-factory.js | 6 +- src/tau.newCharts.js | 3 +- src/units-registry.js | 11 + 7 files changed, 505 insertions(+), 288 deletions(-) create mode 100644 src/elements/coords.cartesian.js create mode 100644 src/elements/element.point.js diff --git a/examples/gpl.html b/examples/gpl.html index 0c749b81a..498818f86 100644 --- a/examples/gpl.html +++ b/examples/gpl.html @@ -24,30 +24,8 @@ @@ -60,255 +38,293 @@ diff --git a/src/charts/tau.gpl.js b/src/charts/tau.gpl.js index 786fb0fa8..a43ee7485 100644 --- a/src/charts/tau.gpl.js +++ b/src/charts/tau.gpl.js @@ -1,18 +1,10 @@ -import {DSLReader} from '../dsl-reader'; import {Emitter} from '../event'; -import {SpecEngineFactory} from '../spec-engine-factory'; -import {LayoutEngineFactory} from '../layout-engine-factory'; import {utils} from '../utils/utils'; import {utilsDom} from '../utils/utils-dom'; -import {CSS_PREFIX} from '../const'; -import {UnitDomainMixin} from '../unit-domain-mixin'; import {UnitsRegistry} from '../units-registry'; -import {DataProcessor} from '../data-processor'; import {getLayout} from '../utils/layuot-template'; - -import {ScalesFactory} from '../unit-domain-mixin'; - - +import {ScalesFactory} from '../scales-factory'; +import {CSS_PREFIX} from '../const'; var FramesAlgebra = { @@ -80,10 +72,12 @@ export class GPL extends Emitter { }; this._layout = getLayout(); - this.initialize(config); + this._initialize(config); } - initialize(config) { + _initialize(config) { + + this.config = config; this.unitHub = UnitsRegistry; @@ -98,26 +92,75 @@ export class GPL extends Emitter { {}); this.trans = config.trans; + } + + render(target, xSize) { + + var containerNode = d3.select(target).node(); + if (containerNode === null) { + throw new Error('Target element not found'); + } + + containerNode.appendChild(this._layout.layout); + var container = d3.select(this._layout.content); + + this._layout.content.innerHTML = ''; + var size = _.clone(xSize) || {}; + if (!size.width || !size.height) { + size = _.defaults(size, utilsDom.getContainerSize(this._layout.content.parentNode)); + } // expand units structure - this.root = expandUnitsStructure(config.unit); + this.root = this.expandUnitsStructure(this.config.unit); + + console.log(JSON.stringify(this.root, null, 4)); + + // throw 1; + + this.root.options = { + container: container.append("svg").attr("class", CSS_PREFIX + 'svg').attr("width", size.width).attr("height", size.height), + left : 0, + top : 0, + width : size.width, + height : size.height + }; + + this.drawUnitsStructure(this.root); } expandUnitsStructure(rootUnit) { var buildRecursively = (root, parentFramePipe) => { - // TODO: detached_cross - to avoid parent frame inheritance - var expr = this.parseExpression(root.expr); - - root.frames = expr.exec().map((tuple) => { - var pipe = parentFramePipe.concat([{where: tuple}]); - return { - key: tuple, + var expr; + var tuples; + + if (root.expr) { + // TODO: detached_cross - to avoid parent frame inheritance + expr = this.parseExpression(root.expr); + tuples = expr.exec(); + } + else { + expr = {source: '/'}; + tuples = [null]; + } + + root.frames = tuples.map((tuple) => { + var pipe = parentFramePipe.concat([{type: 'where', args: tuple}]); + var item = { source: expr.source, - pipe: pipe, - unit: root.unit.map((unit) => buildRecursively(utils.clone(unit), pipe)) + pipe: pipe }; + + if (tuple) { + item.key = tuple; + } + + if (root.unit) { + item.unit = root.unit.map((unit) => buildRecursively(utils.clone(unit), pipe)); + } + + return item; }); return root; @@ -128,23 +171,44 @@ export class GPL extends Emitter { drawUnitsStructure() { - var drawRecursively = (root) => { + var drawRecursively = (rootConf) => { + + var UnitClass = this.unitHub.get(rootConf.type); - var UnitClass = this.unitHub.get(root.type); + rootConf.x = this.scales[rootConf.x]; + rootConf.y = this.scales[rootConf.y]; - var unitNode = new UnitClass(); + var unitNode = new UnitClass(rootConf); - root.frames.map((frame) => { - unitNode.drawFrame(frame); - drawRecursively(frame.unit); + var frames = rootConf.frames.map((frame) => { + var source = this.sources[frame.source]; + var transf = this.trans; + var fnPipe = (memo, cfg) => { + return transf[cfg.type](memo, cfg.args); + }; + frame.data = frame.pipe.reduce(fnPipe, source); + return frame; }); - return root; + unitNode + .drawLayout() + .drawFrames(frames) + .map((unit) => drawRecursively(unit)); + + return rootConf; }; return drawRecursively(this.root); } + // drawUnitsStructure({ + // container: gridContainer, + // left: xScale(xxx.$where[node.x.scaleDim]) - incX / 2, + // top : top, + // width : incX, + // height: height + // }) + parseExpression(sExpression) { var funcName = sExpression[0]; var funcArgs = sExpression.slice(1).map((scaleName) => this.scales[scaleName]); diff --git a/src/elements/coords.cartesian.js b/src/elements/coords.cartesian.js new file mode 100644 index 000000000..e745ed5cb --- /dev/null +++ b/src/elements/coords.cartesian.js @@ -0,0 +1,75 @@ +import {utilsDraw} from '../utils/utils-draw'; +import {CSS_PREFIX} from '../const'; +import {utils} from '../utils/utils'; +import {TMatrix} from '../matrix'; + +export class Cartesian { + + constructor(config) { + super(); + + this.config = config; + } + + drawLayout() { + + var node = this.config; + + var options = node.options; + var padding = node.guide.padding; + + node.x.guide = node.guide.x; + node.y.guide = node.guide.y; + + var L = options.left + padding.l; + var T = options.top + padding.t; + + var W = options.width - (padding.l + padding.r); + var H = options.height - (padding.t + padding.b); + + node.x.scaleObj = node.x.init([0, W]); + node.y.scaleObj = node.y.init([H, 0]); + + node.x.guide.size = W; + node.y.guide.size = H; + + var X_AXIS_POS = [0, H + node.guide.x.padding]; + var Y_AXIS_POS = [0 - node.guide.y.padding, 0]; + + var container = options + .container + .append('g') + .attr('class', CSS_PREFIX + 'cell ' + 'cell') + .attr('transform', utilsDraw.translate(L, T)) + .datum({'$where': node.$where}); + + if (!node.x.guide.hide) { + utilsDraw.fnDrawDimAxis.call(container, node.x, X_AXIS_POS, W); + } + + if (!node.y.guide.hide) { + utilsDraw.fnDrawDimAxis.call(container, node.y, Y_AXIS_POS, H); + } + + this.grid = utilsDraw.fnDrawGrid.call(container, node, H, W); + this.W = W; + this.H = H; + + return this; + } + + drawFrames(frames) { + return frames.map((f) => { + if (true || !f.key) { + f.unit.options = { + container : this.grid, + left : 0, + top : 0, + width : this.W, + height : this.H + }; + } + return f.unit; + }); + } +} \ No newline at end of file diff --git a/src/elements/element.point.js b/src/elements/element.point.js new file mode 100644 index 000000000..ee0be9464 --- /dev/null +++ b/src/elements/element.point.js @@ -0,0 +1,46 @@ +import {CSS_PREFIX} from '../const'; + +export class Point { + + constructor(config) { + super(); + + this.config = config; + this.xScale = config.x; + this.yScale = config.y; + this.color = config.color || (() => ''); + this.size = config.size || (() => 5); + } + + drawLayout() { + return this; + } + + drawFrames(frames) { + + var canvas = this.config.options.container; + + var xScale = this.xScale; + var yScale = this.yScale; + var cScale = this.color; + var sScale = this.size; + + var update = function () { + return this + .attr('r', (d) => sScale(d[sScale.dim])) + .attr('cx', (d) => xScale(d[xScale.dim])) + .attr('cy', (d) => yScale(d[yScale.dim])) + .attr('class', (d) => `${CSS_PREFIX}dot dot i-role-element i-role-datum ${cScale(d[cScale.dim])}`); + }; + + frames.map((frame) => { + var elements; + elements = canvas.selectAll('.dot').data(frame.data); + elements.call(update); + elements.exit().remove(); + elements.enter().append('circle').call(update); + }); + + return []; + } +} \ No newline at end of file diff --git a/src/scales-factory.js b/src/scales-factory.js index d4d530e56..7dc3af1ce 100644 --- a/src/scales-factory.js +++ b/src/scales-factory.js @@ -128,7 +128,11 @@ var scalesStrategies = { return { init: function(interval) { return d3Domain.rangePoints(interval, 1); - } + }, + + scaleDim: props.dim, + + scaleType: 'ordinal' }; }, diff --git a/src/tau.newCharts.js b/src/tau.newCharts.js index 2de598a6e..020a4f900 100644 --- a/src/tau.newCharts.js +++ b/src/tau.newCharts.js @@ -1,4 +1,5 @@ import {utilsDom} from './utils/utils-dom'; +import {GPL} from './charts/tau.gpl'; import {Plot} from './charts/tau.plot'; import {Chart} from './charts/tau.chart'; import {UnitDomainMixin} from './unit-domain-mixin'; @@ -115,7 +116,7 @@ api.UnitsRegistry .add('ELEMENT.POINT', nodeMap['ELEMENT.POINT']) .add('ELEMENT.LINE', nodeMap['ELEMENT.LINE']) .add('ELEMENT.INTERVAL', nodeMap['ELEMENT.INTERVAL']); -export {Plot, Chart, __api__, api}; +export {GPL, Plot, Chart, __api__, api}; diff --git a/src/units-registry.js b/src/units-registry.js index 4287243ce..ca61b30a5 100644 --- a/src/units-registry.js +++ b/src/units-registry.js @@ -1,3 +1,6 @@ +import {Cartesian} from './elements/coords.cartesian'; +import {Point} from './elements/element.point'; + var UnitsMap = {}; var UnitsRegistry = { @@ -12,6 +15,14 @@ var UnitsRegistry = { get: (unitType) => { + if (unitType === 'RECT') { + return Cartesian; + } + + if (unitType === 'POINT') { + return Point; + } + if (!UnitsMap.hasOwnProperty(unitType)) { throw new Error('Unknown unit type: ' + unitType); } From b46518b8671bf82d81b590814872563832e8edaf Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Wed, 18 Feb 2015 20:57:24 +0300 Subject: [PATCH 03/93] Sampling practical solution (facet) --- examples/gpl.html | 70 +++++++++++++++---- src/charts/tau.gpl.js | 116 ++++++++++++------------------- src/elements/coords.cartesian.js | 57 +++++++++++---- src/elements/element.point.js | 23 +++--- src/scales-factory.js | 12 +++- 5 files changed, 168 insertions(+), 110 deletions(-) diff --git a/examples/gpl.html b/examples/gpl.html index 498818f86..b550b04dc 100644 --- a/examples/gpl.html +++ b/examples/gpl.html @@ -25,7 +25,7 @@ @@ -249,14 +249,16 @@ sources: { '/': { dims: { + story : {type: 'category'}, + bug : {type: 'category'}, project : {type: 'category'}, team : {type: 'category'}, count : {type: 'measure'}, date : {type: 'measure'} }, data: [ - {project: 'TP3', team: 'alpha', count: 10, date: new Date('2014-01-05')}, - {project: 'tau', team: 'xbeta', count: 15, date: new Date('2014-10-05')} + {project: 'TP3', team: 'alpha', story: 'A1', bug: 'ISSUE1', count: 10, date: new Date('2014-01-05')}, + {project: 'tau', team: 'xbeta', story: 'B1', bug: 'ISSUE2', count: 15, date: new Date('2014-10-05')} ] } }, @@ -277,19 +279,23 @@ }, scales: { - 'proj': {type: 'ordinal', name: 'A', fitToFrame: true, source: '/', dim: 'project'}, - 'team': {type: 'ordinal', name: 'B', fitToFrame: true, source: '/', dim: 'team'} + 'story' : {type: 'ordinal', name: 'S', fitToFrame: true, source: '/', dim: 'story'}, + 'bug' : {type: 'ordinal', name: 'B', fitToFrame: true, source: '/', dim: 'bug'}, + 'proj' : {type: 'ordinal', name: 'P', fitToFrame: true, source: '/', dim: 'project'}, + 'team' : {type: 'ordinal', name: 'T', fitToFrame: true, source: '/', dim: 'team'} }, unit: { type: 'RECT', x: 'proj', y: 'team', + expr: ['cross', '/', false, 'project', 'team'], guide: { padding: {l: 40, r: 0, t: 0, b: 40}, showGridLines: 'xy', x: { - padding: 0, + textAnchor: 'middle', + padding: 10, hide: false, scaleOrient: 'bottom', rotate: 0, @@ -298,25 +304,59 @@ tickFormatWordWrapLimit: 100 }, y: { - padding: 0, + //textAnchor: 'middle', + padding: 10, hide: false, scaleOrient: 'left', rotate: -90, density: 0, - label: {text: 'team', rotate: 90, padding: 0, textAnchor: 'middle'}, + label: {text: 'team', rotate: -90, padding: 20, textAnchor: 'middle'}, tickFormatWordWrapLimit: 100 } }, unit: [ { - type: 'POINT', - x: 'proj', - y: 'team', + type: 'RECT', + x: 'bug', + y: 'story', + expr: ['none', '/', true], guide: { - padding: {l: 0, r: 0, t: 0, b: 0}, - x: {padding: 0, hide: false}, - y: {padding: 0, hide: false} - } + padding: {l: 40, r: 0, t: 0, b: 40}, + showGridLines: 'xy', + x: { + textAnchor: 'middle', + padding: 10, + hide: false, + scaleOrient: 'bottom', + rotate: 0, + density: 20, + label: {text: 'bug', rotate: 0, padding: 20, textAnchor: 'middle'}, + tickFormatWordWrapLimit: 100 + }, + y: { + //textAnchor: 'middle', + padding: 10, + hide: false, + scaleOrient: 'left', + rotate: -90, + density: 0, + label: {text: 'story', rotate: -90, padding: 20, textAnchor: 'middle'}, + tickFormatWordWrapLimit: 100 + } + }, + unit: [ + { + type: 'POINT', + x: 'bug', + y: 'story', + expr: ['none', '/', true], + guide: { + padding: {l: 0, r: 0, t: 0, b: 0}, + x: {padding: 0, hide: false}, + y: {padding: 0, hide: false} + } + } + ] } ] } diff --git a/src/charts/tau.gpl.js b/src/charts/tau.gpl.js index a43ee7485..18a9057ec 100644 --- a/src/charts/tau.gpl.js +++ b/src/charts/tau.gpl.js @@ -8,19 +8,18 @@ import {CSS_PREFIX} from '../const'; var FramesAlgebra = { - 'cross': function(scaleX, scaleY) { + 'cross': function(dataFn, dimX, dimY) { - var dimX = scaleX.dim; - var dimY = scaleY.dim; + var convert = (v) => (v instanceof Date) ? v.getTime() : v; + + var data = dataFn(); - var domainX = scaleX.domain(); - var domainY = scaleY.domain(); + var domainX = _(data).chain().pluck(dimX).unique(convert).value(); + var domainY = _(data).chain().pluck(dimY).unique(convert).value(); var domX = domainX.length === 0 ? [null] : domainX; var domY = domainY.length === 0 ? [null] : domainY; - var convert = (v) => (v instanceof Date) ? v.getTime() : v; - return _(domY).reduce( (memo, rowVal) => { @@ -38,9 +37,12 @@ var FramesAlgebra = { return r; })); - }, []); + }, + + 'none': function(datus, dimX, dimY) { + return [null]; } }; @@ -52,24 +54,8 @@ export class GPL extends Emitter { super(); - // config.units - - // config.sources - // config.sources.* - // config.sources.*.dims - // config.sources.*.data - - // config.scales - - // config.trans - - // config.unit - this._svg = null; - this._filtersStore = { - filters: {}, - tick: 0 - }; + this._filtersStore = {filters: {}, tick: 0}; this._layout = getLayout(); this._initialize(config); @@ -79,7 +65,7 @@ export class GPL extends Emitter { this.config = config; - this.unitHub = UnitsRegistry; + this.unitSet = UnitsRegistry; this.sources = config.sources; @@ -96,29 +82,27 @@ export class GPL extends Emitter { render(target, xSize) { - var containerNode = d3.select(target).node(); - if (containerNode === null) { + var targetNode = d3.select(target).node(); + if (targetNode === null) { throw new Error('Target element not found'); } - containerNode.appendChild(this._layout.layout); + targetNode.appendChild(this._layout.layout); + + var containerNode = this._layout.content; var container = d3.select(this._layout.content); + containerNode.innerHTML = ''; - this._layout.content.innerHTML = ''; var size = _.clone(xSize) || {}; if (!size.width || !size.height) { - size = _.defaults(size, utilsDom.getContainerSize(this._layout.content.parentNode)); + size = _.defaults(size, utilsDom.getContainerSize(containerNode.parentNode)); } // expand units structure this.root = this.expandUnitsStructure(this.config.unit); - console.log(JSON.stringify(this.root, null, 4)); - - // throw 1; - this.root.options = { - container: container.append("svg").attr("class", CSS_PREFIX + 'svg').attr("width", size.width).attr("height", size.height), + container: container.append("svg").attr(_.extend({'class': `${CSS_PREFIX}svg`}, size)), left : 0, top : 0, width : size.width, @@ -130,23 +114,13 @@ export class GPL extends Emitter { expandUnitsStructure(rootUnit) { - var buildRecursively = (root, parentFramePipe) => { - - var expr; - var tuples; + var buildRecursively = (root, parentPipe) => { - if (root.expr) { - // TODO: detached_cross - to avoid parent frame inheritance - expr = this.parseExpression(root.expr); - tuples = expr.exec(); - } - else { - expr = {source: '/'}; - tuples = [null]; - } + // TODO: detached_cross - to avoid parent frame inheritance + var expr = this.parseExpression(root.expr, parentPipe); - root.frames = tuples.map((tuple) => { - var pipe = parentFramePipe.concat([{type: 'where', args: tuple}]); + root.frames = expr.exec().map((tuple) => { + var pipe = parentPipe.concat([{type: 'where', args: tuple}]); var item = { source: expr.source, pipe: pipe @@ -173,7 +147,7 @@ export class GPL extends Emitter { var drawRecursively = (rootConf) => { - var UnitClass = this.unitHub.get(rootConf.type); + var UnitClass = this.unitSet.get(rootConf.type); rootConf.x = this.scales[rootConf.x]; rootConf.y = this.scales[rootConf.y]; @@ -181,12 +155,11 @@ export class GPL extends Emitter { var unitNode = new UnitClass(rootConf); var frames = rootConf.frames.map((frame) => { - var source = this.sources[frame.source]; - var transf = this.trans; - var fnPipe = (memo, cfg) => { - return transf[cfg.type](memo, cfg.args); - }; - frame.data = frame.pipe.reduce(fnPipe, source); + var data = this.sources[frame.source].data; + var trans = this.trans; + frame.data = frame.pipe.reduce( + (data, cfg) => trans[cfg.type](data, cfg.args), + (data)); return frame; }); @@ -201,23 +174,24 @@ export class GPL extends Emitter { return drawRecursively(this.root); } - // drawUnitsStructure({ - // container: gridContainer, - // left: xScale(xxx.$where[node.x.scaleDim]) - incX / 2, - // top : top, - // width : incX, - // height: height - // }) + parseExpression(sExpression, parentPipe) { - parseExpression(sExpression) { var funcName = sExpression[0]; - var funcArgs = sExpression.slice(1).map((scaleName) => this.scales[scaleName]); + var dataName = sExpression[1]; + var inheritQ = sExpression[2]; + var funcArgs = sExpression.slice(3); + + var dataFn = inheritQ ? + (() => parentPipe.reduce( + (data, cfg) => this.trans[cfg.type](data, cfg.args), + (this.sources[dataName].data))) : + (() => this.sources[dataName].data); return { - source: funcArgs[0].source, - func: FramesAlgebra[funcName], - args: funcArgs, - exec: () => FramesAlgebra[funcName](...funcArgs) + source : dataName, + func : FramesAlgebra[funcName], + args : funcArgs, + exec : () => FramesAlgebra[funcName](...[dataFn].concat(funcArgs)) }; } } \ No newline at end of file diff --git a/src/elements/coords.cartesian.js b/src/elements/coords.cartesian.js index e745ed5cb..fcb82bce9 100644 --- a/src/elements/coords.cartesian.js +++ b/src/elements/coords.cartesian.js @@ -27,8 +27,8 @@ export class Cartesian { var W = options.width - (padding.l + padding.r); var H = options.height - (padding.t + padding.b); - node.x.scaleObj = node.x.init([0, W]); - node.y.scaleObj = node.y.init([H, 0]); + this.x = node.x.scaleObj = node.x.init([0, W]); + this.y = node.y.scaleObj = node.y.init([H, 0]); node.x.guide.size = W; node.y.guide.size = H; @@ -59,17 +59,46 @@ export class Cartesian { } drawFrames(frames) { - return frames.map((f) => { - if (true || !f.key) { - f.unit.options = { - container : this.grid, - left : 0, - top : 0, - width : this.W, - height : this.H - }; - } - return f.unit; - }); + var self = this; + return frames.reduce( + (memo, frame) => { + var mapper; + if (frame.key) { + var coordX = self.x(frame.key[self.x.dim]); + var coordY = self.y(frame.key[self.y.dim]); + + var xDomain = self.x.domain(); + var yDomain = self.y.domain(); + + var xPart = self.W / xDomain.length; + var yPart = self.H / yDomain.length; + + mapper = (unit) => { + unit.options = { + container : self.grid, + left : coordX - xPart / 2, + top : coordY - yPart / 2, + width : xPart, + height : yPart + }; + return unit; + }; + } + else { + mapper = (unit) => { + unit.options = { + container : self.grid, + left : 0, + top : 0, + width : self.W, + height : self.H + }; + return unit; + }; + } + + return memo.concat(frame.unit.map(mapper)); + }, + []); } } \ No newline at end of file diff --git a/src/elements/element.point.js b/src/elements/element.point.js index ee0be9464..4de7c40e3 100644 --- a/src/elements/element.point.js +++ b/src/elements/element.point.js @@ -6,10 +6,11 @@ export class Point { super(); this.config = config; - this.xScale = config.x; - this.yScale = config.y; - this.color = config.color || (() => ''); - this.size = config.size || (() => 5); + + this.xScale = config.x.init([0, config.options.width]); + this.yScale = config.y.init([config.options.height, 0]); + this.color = config.color || (() => ''); + this.size = config.size || (() => 5); } drawLayout() { @@ -26,11 +27,15 @@ export class Point { var sScale = this.size; var update = function () { - return this - .attr('r', (d) => sScale(d[sScale.dim])) - .attr('cx', (d) => xScale(d[xScale.dim])) - .attr('cy', (d) => yScale(d[yScale.dim])) - .attr('class', (d) => `${CSS_PREFIX}dot dot i-role-element i-role-datum ${cScale(d[cScale.dim])}`); + + var props = { + 'r' : (d) => sScale(d[sScale.dim]), + 'cx' : (d) => xScale(d[xScale.dim]), + 'cy' : (d) => yScale(d[yScale.dim]), + 'class' : (d) => `${CSS_PREFIX}dot dot i-role-element i-role-datum ${cScale(d[cScale.dim])}` + }; + + return this.attr(props); }; frames.map((frame) => { diff --git a/src/scales-factory.js b/src/scales-factory.js index 7dc3af1ce..eec64c0cb 100644 --- a/src/scales-factory.js +++ b/src/scales-factory.js @@ -127,9 +127,19 @@ var scalesStrategies = { var d3Domain = d3.scale.ordinal().domain(varSet); return { init: function(interval) { - return d3Domain.rangePoints(interval, 1); + var scale = d3Domain.rangePoints(interval, 1); + scale.dim = props.dim; + return scale; + }, + + domain: function() { + return varSet; }, + dim: props.dim, + + source: props.source, + scaleDim: props.dim, scaleType: 'ordinal' From 5665e74402cc49975607863795a189b0fd6312b8 Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Thu, 19 Feb 2015 19:59:47 +0300 Subject: [PATCH 04/93] Initialize scales [color, size, pos-ordinal]. Clever units initialization. --- examples/gpl.html | 14 ++++-- src/charts/tau.gpl.js | 75 +++++++++++++++++------------ src/elements/coords.cartesian.js | 23 ++++++--- src/elements/element.point.js | 17 ++++--- src/scales-factory.js | 81 ++++++++++++++------------------ 5 files changed, 115 insertions(+), 95 deletions(-) diff --git a/examples/gpl.html b/examples/gpl.html index b550b04dc..31e08c8cf 100644 --- a/examples/gpl.html +++ b/examples/gpl.html @@ -247,6 +247,10 @@ var xSpec = { sources: { + '?': { + dims: {}, + data: [] + }, '/': { dims: { story : {type: 'category'}, @@ -279,10 +283,12 @@ }, scales: { - 'story' : {type: 'ordinal', name: 'S', fitToFrame: true, source: '/', dim: 'story'}, - 'bug' : {type: 'ordinal', name: 'B', fitToFrame: true, source: '/', dim: 'bug'}, - 'proj' : {type: 'ordinal', name: 'P', fitToFrame: true, source: '/', dim: 'project'}, - 'team' : {type: 'ordinal', name: 'T', fitToFrame: true, source: '/', dim: 'team'} + 'size:default' : {type: 'size', name: 'DefSize', fitToFrame: false, source: '?', mid: 5}, + 'color:default' : {type: 'color', name: 'DefColor', fitToFrame: false, source: '?', brewer: null}, + 'story' : {type: 'ordinal', name: 'S', fitToFrame: false, source: '/', dim: 'story'}, + 'bug' : {type: 'ordinal', name: 'B', fitToFrame: false, source: '/', dim: 'bug'}, + 'proj' : {type: 'ordinal', name: 'P', fitToFrame: false, source: '/', dim: 'project'}, + 'team' : {type: 'ordinal', name: 'T', fitToFrame: false, source: '/', dim: 'team'} }, unit: { diff --git a/src/charts/tau.gpl.js b/src/charts/tau.gpl.js index 18a9057ec..ddb789a5c 100644 --- a/src/charts/tau.gpl.js +++ b/src/charts/tau.gpl.js @@ -46,7 +46,20 @@ var FramesAlgebra = { } }; +var calcBaseFrame = (unit, baseFrame) => { + var tmpFrame = _.pick(baseFrame || {}, 'source', 'pipe'); + + var srcAlias = unit.expr[1]; + var bInherit = unit.expr[2]; + var ownFrame = {source: srcAlias, pipe: []}; + + if (bInherit && (ownFrame.source !== tmpFrame.source)) { + throw new Error(`base [${tmpFrame.source}] and own [${ownFrame.source}] sources should be equal to apply inheritance`); + } + + return bInherit ? tmpFrame : ownFrame; +}; export class GPL extends Emitter { @@ -69,13 +82,9 @@ export class GPL extends Emitter { this.sources = config.sources; - var scalesCreator = new ScalesFactory(config.sources); - this.scales = _.keys(config.scales).reduce( - (memo, key) => { - memo[key] = scalesCreator.create(config.scales[key]); - return memo; - }, - {}); + this.scalesCreator = new ScalesFactory(config.sources); + + this.scales = config.scales; this.trans = config.trans; } @@ -102,7 +111,7 @@ export class GPL extends Emitter { this.root = this.expandUnitsStructure(this.config.unit); this.root.options = { - container: container.append("svg").attr(_.extend({'class': `${CSS_PREFIX}svg`}, size)), + container: container.append('svg').attr(_.extend({'class': `${CSS_PREFIX}svg`}, size)), left : 0, top : 0, width : size.width, @@ -116,7 +125,6 @@ export class GPL extends Emitter { var buildRecursively = (root, parentPipe) => { - // TODO: detached_cross - to avoid parent frame inheritance var expr = this.parseExpression(root.expr, parentPipe); root.frames = expr.exec().map((tuple) => { @@ -145,50 +153,57 @@ export class GPL extends Emitter { drawUnitsStructure() { - var drawRecursively = (rootConf) => { + var continueDrawRecursively = (rootConf, rootFrame) => { - var UnitClass = this.unitSet.get(rootConf.type); + var dataFrame = this.datify(calcBaseFrame(rootConf, rootFrame)); - rootConf.x = this.scales[rootConf.x]; - rootConf.y = this.scales[rootConf.y]; + var UnitClass = this.unitSet.get(rootConf.type); var unitNode = new UnitClass(rootConf); - var frames = rootConf.frames.map((frame) => { - var data = this.sources[frame.source].data; - var trans = this.trans; - frame.data = frame.pipe.reduce( - (data, cfg) => trans[cfg.type](data, cfg.args), - (data)); - return frame; - }); - unitNode - .drawLayout() - .drawFrames(frames) - .map((unit) => drawRecursively(unit)); + .drawLayout((type, alias, settings) => { + + // type is one of: + // - pos + // - size + // - color + // - shape + var name = alias ? alias : `${type}:default`; + + return this.scalesCreator.create(this.scales[name], dataFrame, settings); + }) + .drawFrames(rootConf.frames.map(this.datify.bind(this)), continueDrawRecursively); return rootConf; }; - return drawRecursively(this.root); + return continueDrawRecursively(this.root); + } + + datify(frame) { + var data = this.sources[frame.source].data; + var trans = this.trans; + frame.take = () => frame.pipe.reduce((data, pipeCfg) => trans[pipeCfg.type](data, pipeCfg.args), data); + frame.data = frame.take(); + return frame; } parseExpression(sExpression, parentPipe) { var funcName = sExpression[0]; - var dataName = sExpression[1]; + var srcAlias = sExpression[1]; var inheritQ = sExpression[2]; var funcArgs = sExpression.slice(3); var dataFn = inheritQ ? (() => parentPipe.reduce( (data, cfg) => this.trans[cfg.type](data, cfg.args), - (this.sources[dataName].data))) : - (() => this.sources[dataName].data); + (this.sources[srcAlias].data))) : + (() => this.sources[srcAlias].data); return { - source : dataName, + source : srcAlias, func : FramesAlgebra[funcName], args : funcArgs, exec : () => FramesAlgebra[funcName](...[dataFn].concat(funcArgs)) diff --git a/src/elements/coords.cartesian.js b/src/elements/coords.cartesian.js index fcb82bce9..9901d962d 100644 --- a/src/elements/coords.cartesian.js +++ b/src/elements/coords.cartesian.js @@ -11,24 +11,30 @@ export class Cartesian { this.config = config; } - drawLayout() { + drawLayout(fnCreateScale) { var node = this.config; var options = node.options; var padding = node.guide.padding; - node.x.guide = node.guide.x; - node.y.guide = node.guide.y; - var L = options.left + padding.l; var T = options.top + padding.t; var W = options.width - (padding.l + padding.r); var H = options.height - (padding.t + padding.b); - this.x = node.x.scaleObj = node.x.init([0, W]); - this.y = node.y.scaleObj = node.y.init([H, 0]); + this.x = this.xScale = fnCreateScale('pos', node.x, [0, W]); + this.y = this.yScale = fnCreateScale('pos', node.y, [H, 0]); + + node.x = this.xScale; + node.y = this.yScale; + + node.x.scaleObj = this.xScale; + node.y.scaleObj = this.yScale; + + node.x.guide = node.guide.x; + node.y.guide = node.guide.y; node.x.guide.size = W; node.y.guide.size = H; @@ -58,8 +64,9 @@ export class Cartesian { return this; } - drawFrames(frames) { + drawFrames(frames, continuation) { var self = this; + return frames.reduce( (memo, frame) => { var mapper; @@ -97,6 +104,8 @@ export class Cartesian { }; } + frame.unit.map((u) => continuation(mapper(u), frame)); + return memo.concat(frame.unit.map(mapper)); }, []); diff --git a/src/elements/element.point.js b/src/elements/element.point.js index 4de7c40e3..bb31e6ddc 100644 --- a/src/elements/element.point.js +++ b/src/elements/element.point.js @@ -6,14 +6,17 @@ export class Point { super(); this.config = config; - - this.xScale = config.x.init([0, config.options.width]); - this.yScale = config.y.init([config.options.height, 0]); - this.color = config.color || (() => ''); - this.size = config.size || (() => 5); } - drawLayout() { + drawLayout(fnCreateScale) { + + var config = this.config; + + this.xScale = fnCreateScale('pos', config.x, [0, config.options.width]); + this.yScale = fnCreateScale('pos', config.y, [config.options.height, 0]); + this.color = fnCreateScale('color', config.color, {}); + this.size = fnCreateScale('size', config.size, {}); + return this; } @@ -40,7 +43,7 @@ export class Point { frames.map((frame) => { var elements; - elements = canvas.selectAll('.dot').data(frame.data); + elements = canvas.selectAll('.dot').data(frame.take()); elements.call(update); elements.exit().remove(); elements.enter().append('circle').call(update); diff --git a/src/scales-factory.js b/src/scales-factory.js index eec64c0cb..744ffc2e2 100644 --- a/src/scales-factory.js +++ b/src/scales-factory.js @@ -53,24 +53,25 @@ var scalesStrategies = { throw new Error('This brewer is not supported'); } - var wrap = func; - - return { - init: function() { - - wrap.legend = (v) => { - - // var value = varSet.extract(v); - var value = v; - var label = (props.tickLabel) ? ((v || {})[props.tickLabel]) : (value); - var color = func(value); - - return {value, color, label}; - }; - - return wrap; - } - }; + return func; + + //var wrap = func; + //return { + // init: function() { + // + // wrap.legend = (v) => { + // + // // var value = varSet.extract(v); + // var value = v; + // var label = (props.tickLabel) ? ((v || {})[props.tickLabel]) : (value); + // var color = func(value); + // + // return {value, color, label}; + // }; + // + // return wrap; + // } + //}; }, 'size': (varSet, props) => { @@ -116,34 +117,20 @@ var scalesStrategies = { return (minSize + (f(posX) * k)); }; - return { - init: function (interval) { - return func; - } - }; + return func; }, - 'ordinal': (varSet, props) => { - var d3Domain = d3.scale.ordinal().domain(varSet); - return { - init: function(interval) { - var scale = d3Domain.rangePoints(interval, 1); - scale.dim = props.dim; - return scale; - }, - - domain: function() { - return varSet; - }, + 'ordinal': (varSet, props, interval) => { - dim: props.dim, - - source: props.source, - - scaleDim: props.dim, + var d3Domain = d3.scale.ordinal().domain(varSet); - scaleType: 'ordinal' - }; + var scale = d3Domain.rangePoints(interval, 1); + scale.dim = props.dim; + scale.domain = () => varSet; + scale.source = props.source; + scale.scaleDim = props.dim; + scale.scaleType = 'ordinal'; + return scale; }, 'linear': (vars, props) => { @@ -234,16 +221,16 @@ export class ScalesFactory { this.sources = sources; } - create(scaleConfig) { + create(scaleConfig, frame, interval) { var dim = scaleConfig.dim; var src = scaleConfig.source; - var meta = this.sources[src].dims[dim]; - var data = this.sources[src].data; + var type = (this.sources[src].dims[dim] || {}).type; + var data = scaleConfig.fitToFrame ? frame.take() : this.sources[scaleConfig.source].data; - var vars = _(data).chain().pluck(dim).uniq(map_value(meta.type)).value(); + var vars = _(data).chain().pluck(dim).uniq(map_value(type)).value(); - return scalesStrategies[scaleConfig.type](vars, scaleConfig); + return scalesStrategies[scaleConfig.type](vars, scaleConfig, interval); } } \ No newline at end of file From 8dbe0171b45d68a09cce4a62285f2cd5e79e1abe Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Fri, 20 Feb 2015 20:03:13 +0300 Subject: [PATCH 05/93] Add dashboard sample --- examples/gpl.html | 1013 +++++++++++++++++++++++------- src/charts/tau.gpl.js | 43 +- src/elements/coords.cartesian.js | 57 ++ src/elements/element.line.js | 96 +++ src/elements/element.point.js | 2 +- src/scales-factory.js | 67 +- src/units-registry.js | 5 + 7 files changed, 1010 insertions(+), 273 deletions(-) create mode 100644 src/elements/element.line.js diff --git a/examples/gpl.html b/examples/gpl.html index 31e08c8cf..804d78009 100644 --- a/examples/gpl.html +++ b/examples/gpl.html @@ -8,10 +8,8 @@ - - @@ -23,9 +21,325 @@ + + + @@ -33,224 +347,309 @@
- +
+ +
- + + + + + + diff --git a/src/charts/tau.gpl.js b/src/charts/tau.gpl.js index ddb789a5c..cf30fb095 100644 --- a/src/charts/tau.gpl.js +++ b/src/charts/tau.gpl.js @@ -41,7 +41,7 @@ var FramesAlgebra = { []); }, - 'none': function(datus, dimX, dimY) { + 'none': function(datus, dimX, dimY, pipe) { return [null]; } }; @@ -104,7 +104,8 @@ export class GPL extends Emitter { var size = _.clone(xSize) || {}; if (!size.width || !size.height) { - size = _.defaults(size, utilsDom.getContainerSize(containerNode.parentNode)); + // size = _.defaults(size, utilsDom.getContainerSize(containerNode.parentNode)); + size = _.defaults(size, utilsDom.getContainerSize(targetNode)); } // expand units structure @@ -125,22 +126,34 @@ export class GPL extends Emitter { var buildRecursively = (root, parentPipe) => { - var expr = this.parseExpression(root.expr, parentPipe); + if (root.expr[0] !== false) { + var expr = this.parseExpression(root.expr, parentPipe); - root.frames = expr.exec().map((tuple) => { - var pipe = parentPipe.concat([{type: 'where', args: tuple}]); - var item = { - source: expr.source, - pipe: pipe - }; + root.transf = root.transf || []; - if (tuple) { - item.key = tuple; - } + root.frames = expr.exec().map((tuple) => { + var pipe = parentPipe.concat([{type: 'where', args: tuple}]).concat(root.transf); + var item = { + source: expr.source, + pipe: pipe + }; - if (root.unit) { - item.unit = root.unit.map((unit) => buildRecursively(utils.clone(unit), pipe)); - } + if (tuple) { + item.key = tuple; + } + + item.unit = (root.unit) ? root.unit.map((unit) => utils.clone(unit)) : []; + + return item; + }); + } + + root.frames.map((item) => { + // key: tuple, + // source: expr.source, + // pipe: pipe + + item.unit.map((unit) => buildRecursively(unit, item.pipe)); return item; }); diff --git a/src/elements/coords.cartesian.js b/src/elements/coords.cartesian.js index 9901d962d..0e4334320 100644 --- a/src/elements/coords.cartesian.js +++ b/src/elements/coords.cartesian.js @@ -9,6 +9,63 @@ export class Cartesian { super(); this.config = config; + + this.config.guide = this.config.guide || {}; + + this.config.guide.showGridLines = 'xy'; + this.config.guide.padding = {l: 50, r: 0, t: 0, b: 50}; + + this.config.guide.x = this.config.guide.x || {}; + this.config.guide.x.cssClass = 'x axis'; + this.config.guide.x = _.defaults( + this.config.guide.x, + { + cssClass: 'x axis', + textAnchor: 'middle', + padding: 10, + hide: false, + scaleOrient: 'bottom', + rotate: 0, + density: 20, + label: {}, + tickFormatWordWrapLimit: 100 + } + ); + + this.config.guide.x.label = _.defaults( + this.config.guide.x.label, + { + text: 'X', + rotate: 0, + padding: 40, + textAnchor: 'middle' + } + ); + + this.config.guide.y = this.config.guide.y || {}; + this.config.guide.y = _.defaults( + this.config.guide.y, + { + cssClass: 'y axis', + textAnchor: 'start', + padding: 10, + hide: false, + scaleOrient: 'left', + rotate: 0, + density: 20, + label: {}, + tickFormatWordWrapLimit: 100 + }); + + this.config.guide.y.label = _.defaults( + this.config.guide.y.label, + { + text: 'Y', + rotate: -90, + padding: 20, + textAnchor: 'middle' + } + ); } drawLayout(fnCreateScale) { diff --git a/src/elements/element.line.js b/src/elements/element.line.js new file mode 100644 index 000000000..85ff664e9 --- /dev/null +++ b/src/elements/element.line.js @@ -0,0 +1,96 @@ +import {CSS_PREFIX} from '../const'; +import {getLineClassesByWidth, getLineClassesByCount} from '../utils/css-class-map'; + +export class Line { + + constructor(config) { + super(); + + this.config = config; + this.config.guide = this.config.guide || {}; + this.config.guide = _.defaults( + this.config.guide, + { + cssClass: '' + } + ); + } + + drawLayout(fnCreateScale) { + + var config = this.config; + + this.xScale = fnCreateScale('pos', config.x, [0, config.options.width]); + this.yScale = fnCreateScale('pos', config.y, [config.options.height, 0]); + this.color = fnCreateScale('color', config.color, {}); + this.size = fnCreateScale('size', config.size, {}); + + return this; + } + + drawFrames(frames) { + + var config = this.config; + + var options = this.config.options; + + var xScale = this.xScale; + var yScale = this.yScale; + var colorScale = this.color; + + var widthClass = getLineClassesByWidth(options.width); + var countClass = getLineClassesByCount(frames.length); + + var updateLines = function () { + this.attr('class', (d) => `${CSS_PREFIX}line i-role-element i-role-datum line ${colorScale(d.key)} ${widthClass} ${countClass} ${config.guide.cssClass}`); + var paths = this.selectAll('path').data((d) => [d.take()]); + paths.call(updatePaths); + paths.enter().append('path').call(updatePaths); + paths.exit().remove(); + }; + + var drawPoints = function (points) { + var update = function () { + return this + .attr('r', 1.5) + .attr('class', (d) => `${CSS_PREFIX}dot-line dot-line i-role-element ${CSS_PREFIX}dot i-role-datum ${colorScale(d[colorScale.dim])}`) + .attr('cx', (d) => xScale(d[xScale.dim])) + .attr('cy', (d) => yScale(d[yScale.dim])); + }; + + var elements = options.container.selectAll('.dot-line').data(points); + elements.call(update); + elements.exit().remove(); + elements.enter().append('circle').call(update); + }; + + var line = d3.svg.line().x((d) => xScale(d[xScale.dim])).y((d) => yScale(d[yScale.dim])); + + if (this.config.guide.interpolate) { + line.interpolate(this.config.guide.interpolate); + } + + var updatePaths = function () { + this.attr('d', line); + }; + + var points = frames.reduce( + function (points, item) { + var values = item.take(); + if (values.length === 1) { + points.push(values[0]); + } + return points; + }, + []); + + if (points.length > 0) { + drawPoints(points); + } + + var lines = options.container.selectAll('.line' + parseInt(Math.random() * 1000)).data(frames); + lines.call(updateLines); + lines.enter().append('g').call(updateLines); + lines.exit().remove(); + } +} \ No newline at end of file diff --git a/src/elements/element.point.js b/src/elements/element.point.js index bb31e6ddc..46d5c047c 100644 --- a/src/elements/element.point.js +++ b/src/elements/element.point.js @@ -43,7 +43,7 @@ export class Point { frames.map((frame) => { var elements; - elements = canvas.selectAll('.dot').data(frame.take()); + elements = canvas.selectAll('.dot' + parseInt(Math.random() * 1000)).data(frame.take()); elements.call(update); elements.exit().remove(); elements.enter().append('circle').call(update); diff --git a/src/scales-factory.js b/src/scales-factory.js index 744ffc2e2..795bac500 100644 --- a/src/scales-factory.js +++ b/src/scales-factory.js @@ -53,6 +53,12 @@ var scalesStrategies = { throw new Error('This brewer is not supported'); } + func.dim = props.dim; + func.domain = () => varSet; + func.source = props.source; + func.scaleDim = props.dim; + func.scaleType = 'color'; + return func; //var wrap = func; @@ -117,6 +123,12 @@ var scalesStrategies = { return (minSize + (f(posX) * k)); }; + func.dim = props.dim; + func.domain = () => varSet; + func.source = props.source; + func.scaleDim = props.dim; + func.scaleType = 'size'; + return func; }, @@ -133,7 +145,7 @@ var scalesStrategies = { return scale; }, - 'linear': (vars, props) => { + 'linear': (vars, props, interval) => { var domain = (props.autoScale) ? utils.autoScale(vars) : d3.extent(vars); @@ -147,14 +159,29 @@ var scalesStrategies = { var d3Domain = d3.scale.linear().domain(varSet); - return { - init: function(interval) { - return d3Domain.rangeRound(interval, 1); - } + var d3Scale = d3Domain.rangeRound(interval, 1); + var scale = (int) => { + var x = int; + if (x > max) {x = max} + if (x < min) {x = min} + return d3Scale(x); }; + + // have to copy properties since d3 produce Function with methods + Object.keys(d3Scale).forEach(function (p) { + return scale[p] = d3Scale[p]; + }); + + scale.dim = props.dim; + scale.domain = () => varSet; + scale.source = props.source; + scale.scaleDim = props.dim; + scale.scaleType = 'linear'; + + return scale; }, - 'period': (vars, props) => { + 'period': (vars, props, interval) => { // extract: ((x) => UnitDomainPeriodGenerator.get(xOptions.period).cast(new Date(x))) @@ -171,14 +198,17 @@ var scalesStrategies = { var d3Domain = d3.scale.ordinal().domain(varSet); - return { - init: function(interval) { - return d3Domain.rangePoints(interval, 1); - } - }; + var scale = d3Domain.rangePoints(interval, 1); + scale.dim = props.dim; + scale.domain = () => varSet; + scale.source = props.source; + scale.scaleDim = props.dim; + scale.scaleType = 'period'; + + return scale; }, - 'time': (vars, props) => { + 'time': (vars, props, interval) => { var domain = d3.extent(vars); var min = (_.isNull(props.min) || _.isUndefined(props.min)) ? domain[0] : new Date(props.min).getTime(); @@ -191,11 +221,14 @@ var scalesStrategies = { var d3Domain = d3.time.scale().domain(varSet); - return { - init: function(interval) { - return d3Domain.range(interval); - } - }; + var scale = d3Domain.range(interval); + scale.dim = props.dim; + scale.domain = () => varSet; + scale.source = props.source; + scale.scaleDim = props.dim; + scale.scaleType = 'time'; + + return scale; } }; diff --git a/src/units-registry.js b/src/units-registry.js index ca61b30a5..a9dd94233 100644 --- a/src/units-registry.js +++ b/src/units-registry.js @@ -1,5 +1,6 @@ import {Cartesian} from './elements/coords.cartesian'; import {Point} from './elements/element.point'; +import {Line} from './elements/element.line'; var UnitsMap = {}; @@ -23,6 +24,10 @@ var UnitsRegistry = { return Point; } + if (unitType === 'LINE') { + return Line; + } + if (!UnitsMap.hasOwnProperty(unitType)) { throw new Error('Unknown unit type: ' + unitType); } From 419d6d27e3b4a42c4d8622e32a369aec82c1dba9 Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Fri, 20 Feb 2015 22:08:27 +0300 Subject: [PATCH 06/93] Pie element is added to the sample dashboard --- examples/gpl.html | 48 +++++++++++++++++- src/elements/coords.cartesian.js | 11 +++-- src/elements/element.pie.js | 85 ++++++++++++++++++++++++++++++++ src/scales-factory.js | 12 +++++ src/tau.newCharts.js | 15 +++++- src/units-registry.js | 21 ++------ 6 files changed, 168 insertions(+), 24 deletions(-) create mode 100644 src/elements/element.pie.js diff --git a/examples/gpl.html b/examples/gpl.html index 804d78009..366355a8d 100644 --- a/examples/gpl.html +++ b/examples/gpl.html @@ -646,7 +646,20 @@ }, data: [ {x: 1, y: 1}, - {x: 2, y: 1} + {x: 2, y: 1}, + {x: 2, y: 2} + ] + }, + + 'o': { + dims: { + label : {type: 'category'}, + value : {type: 'measure'} + }, + data: [ + {"label":"Category A", "value":20}, + {"label":"Category B", "value":50}, + {"label":"Category C", "value":30} ] }, @@ -781,7 +794,11 @@ 'xdate' : {type: 'linear', name: 'P', source: '@', dim: 'xdate'}, 'annotation1' : {type: 'color', name: 'limit', source: '@', dim: 'count', brewer: ['color20-2']}, - 'annotation2' : {type: 'color', name: 'limit', source: '@', dim: 'count', brewer: ['color20-5']} + 'annotation2' : {type: 'color', name: 'limit', source: '@', dim: 'count', brewer: ['color20-5']}, + + 'label' : {type: 'value', source: 'o', dim: 'label'}, + 'piePortion' : {type: 'value', source: 'o', dim: 'value'}, + 'label_color' : {type: 'color', source: 'o', dim: 'label'} }, unit: { @@ -790,6 +807,33 @@ y: 'yScale', expr: [false, '$', false], frames: [ + { + key: {x: 2, y: 2}, + source: '$', + pipe: [], + unit: [ + { + type: 'RECT', + x: 'xScale', + y: 'yScale', + expr: ['none', 'o', false], + guide: { + showGridLines: '', + x: {hide:true}, + y: {hide:true} + }, + unit: [ + { + type: 'PIE', + proportion: 'piePortion', + label: 'label', + color: 'label_color', + expr: ['none', 'o', true] + } + ] + } + ] + }, { key: {x: 2, y: 1}, source: '$', diff --git a/src/elements/coords.cartesian.js b/src/elements/coords.cartesian.js index 0e4334320..5ab46f5bc 100644 --- a/src/elements/coords.cartesian.js +++ b/src/elements/coords.cartesian.js @@ -10,13 +10,14 @@ export class Cartesian { this.config = config; - this.config.guide = this.config.guide || {}; - - this.config.guide.showGridLines = 'xy'; - this.config.guide.padding = {l: 50, r: 0, t: 0, b: 50}; + this.config.guide = _.defaults( + this.config.guide || {}, + { + showGridLines: 'xy', + padding: {l: 50, r: 0, t: 0, b: 50} + }); this.config.guide.x = this.config.guide.x || {}; - this.config.guide.x.cssClass = 'x axis'; this.config.guide.x = _.defaults( this.config.guide.x, { diff --git a/src/elements/element.pie.js b/src/elements/element.pie.js new file mode 100644 index 000000000..43ee59169 --- /dev/null +++ b/src/elements/element.pie.js @@ -0,0 +1,85 @@ +import {CSS_PREFIX} from '../const'; +import {getLineClassesByWidth, getLineClassesByCount} from '../utils/css-class-map'; + +export class Pie { + + constructor(config) { + super(); + + this.config = config; + this.config.guide = this.config.guide || {}; + this.config.guide = _.defaults( + this.config.guide, + { + cssClass: '' + } + ); + } + + drawLayout(fnCreateScale) { + + var config = this.config; + + this.proportionScale = fnCreateScale('value', config.proportion); + this.labelScale = fnCreateScale('value', config.label); + this.colorScale = fnCreateScale('color', config.color, {}); + + return this; + } + + drawFrames(frames) { + + var config = this.config; + + var options = config.options; + + var proportion = this.proportionScale; + var label = this.labelScale; + var color = this.colorScale; + + var w = options.width; + var h = options.height; + var r = h / 2; + + var data = frames[0].take(); + + var vis = options.container + .append("svg:svg") + .data([data]) + .attr("width", w) + .attr("height", h) + .append("svg:g") + .attr("transform", "translate(" + r + "," + r + ")"); + + var pie = d3.layout.pie().value((d) => d[proportion.dim]); + + // declare an arc generator function + var arc = d3.svg.arc().outerRadius(r); + + // select paths, use arc generator to draw + var arcs = vis + .selectAll(".slice") + .data(pie) + .enter().append("g").attr("class", "slice"); + + arcs.append("path") + .attr("class", (d) => { + var dm = d.data || {}; + return color(dm[color.dim]); + }) + .attr("d", (d) => arc(d)); + + // add the text + arcs.append("text") + .attr("transform", (d) => { + d.innerRadius = 0; + d.outerRadius = r; + return "translate(" + arc.centroid(d) + ")"; + }) + .attr("text-anchor", "middle") + .text((d) => { + var dm = d.data || {}; + return label(dm[label.dim]); + }); + } +} \ No newline at end of file diff --git a/src/scales-factory.js b/src/scales-factory.js index 795bac500..4ac988ec5 100644 --- a/src/scales-factory.js +++ b/src/scales-factory.js @@ -228,6 +228,18 @@ var scalesStrategies = { scale.scaleDim = props.dim; scale.scaleType = 'time'; + return scale; + }, + + 'value': (vars, props, interval) => { + + var scale = (x) => x; + scale.dim = props.dim; + scale.domain = () => varSet; + scale.source = props.source; + scale.scaleDim = props.dim; + scale.scaleType = 'value'; + return scale; } }; diff --git a/src/tau.newCharts.js b/src/tau.newCharts.js index 020a4f900..6452502ad 100644 --- a/src/tau.newCharts.js +++ b/src/tau.newCharts.js @@ -10,6 +10,13 @@ import {LayoutEngineFactory} from './layout-engine-factory'; import {FormatterRegistry} from './formatter-registry'; import {nodeMap} from './node-map'; import {UnitsRegistry} from './units-registry'; + + +import {Cartesian} from './elements/coords.cartesian'; +import {Point} from './elements/element.point'; +import {Line} from './elements/element.line'; +import {Pie} from './elements/element.pie'; + var colorBrewers = {}; var plugins = {}; @@ -115,7 +122,13 @@ api.UnitsRegistry .add('COORDS.RECT', nodeMap['COORDS.RECT']) .add('ELEMENT.POINT', nodeMap['ELEMENT.POINT']) .add('ELEMENT.LINE', nodeMap['ELEMENT.LINE']) - .add('ELEMENT.INTERVAL', nodeMap['ELEMENT.INTERVAL']); + .add('ELEMENT.INTERVAL', nodeMap['ELEMENT.INTERVAL']) + + .reg('RECT', Cartesian) + .reg('POINT', Point) + .reg('LINE', Line) + .reg('PIE', Pie); + export {GPL, Plot, Chart, __api__, api}; diff --git a/src/units-registry.js b/src/units-registry.js index a9dd94233..1c53d6c7d 100644 --- a/src/units-registry.js +++ b/src/units-registry.js @@ -1,11 +1,12 @@ -import {Cartesian} from './elements/coords.cartesian'; -import {Point} from './elements/element.point'; -import {Line} from './elements/element.line'; - var UnitsMap = {}; var UnitsRegistry = { + reg: function(unitType, xUnit) { + UnitsMap[unitType] = xUnit; + return this; + }, + add: function(unitType, xUnit) { var unit = {}; unit.draw = (typeof xUnit === 'function') ? xUnit : xUnit.draw; @@ -16,18 +17,6 @@ var UnitsRegistry = { get: (unitType) => { - if (unitType === 'RECT') { - return Cartesian; - } - - if (unitType === 'POINT') { - return Point; - } - - if (unitType === 'LINE') { - return Line; - } - if (!UnitsMap.hasOwnProperty(unitType)) { throw new Error('Unknown unit type: ' + unitType); } From c36bdcecc1d82afc8521ad86255dc21715fb0b28 Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Mon, 23 Feb 2015 16:09:14 +0300 Subject: [PATCH 07/93] Refactor [expr] property to [expression] object Refactor [transf] to [transformation] --- examples/gpl.html | 131 ++++++++++++++++++++++++++++++------------ src/charts/tau.gpl.js | 48 ++++++++++------ 2 files changed, 127 insertions(+), 52 deletions(-) diff --git a/examples/gpl.html b/examples/gpl.html index 366355a8d..ce446b1a8 100644 --- a/examples/gpl.html +++ b/examples/gpl.html @@ -405,7 +405,12 @@ type: 'RECT', x: 'proj', y: 'team', - expr: ['cross', '/', false, 'project', 'team'], + expression: { + inherit: false, + source: '/', + operator: 'cross', + params: ['project', 'team'] + }, guide: { x: {label: {text: 'project'}}, y: {label: {text: 'team'}} @@ -415,7 +420,10 @@ type: 'RECT', x: 'count', y: 'story', - expr: ['none', '/', true], + expression: { + source: '/', + inherit: true + }, guide: { x: {label: {text: 'count'}}, y: {label: {text: 'story'}} @@ -425,7 +433,10 @@ type: 'POINT', x: 'count', y: 'story', - expr: ['none', '/', true] + expression: { + source: '/', + inherit: true + } } ] } @@ -485,12 +496,12 @@ }); }, - 'regression': function(sVal, props) { + 'regression': function(data, props) { var x = props.x; var y = props.y; - var src = sVal.map(function (item) { + var src = data.map(function (item) { var ix = _.isDate(item[x]) ? item[x].getTime() : item[x]; var iy = _.isDate(item[y]) ? item[y].getTime() : item[y]; return [ix, iy]; @@ -552,18 +563,21 @@ scales: { 'size:default' : {type: 'size', name: 'DefSize', source: '?', mid: 5}, 'color:default' : {type: 'color', name: 'DefColor', source: '?', brewer: null}, - 'count' : {type: 'linear', name: 'C', source: '/', dim: 'count', autoScale: true}, - 'xdate' : {type: 'linear', name: 'P', source: '/', dim: 'xdate'}, + 'count' : {type: 'linear', source: '/', dim: 'count', autoScale: true}, + 'xdate' : {type: 'linear', source: '/', dim: 'xdate'}, - 'annotation1' : {type: 'color', name: 'limit', source: '/', dim: 'count', brewer: ['color20-2']}, - 'annotation2' : {type: 'color', name: 'limit', source: '/', dim: 'count', brewer: ['color20-5']} + 'annotation1' : {type: 'color', source: '/', dim: 'count', brewer: ['color20-2']}, + 'annotation2' : {type: 'color', source: '/', dim: 'count', brewer: ['color20-5']} }, unit: { type: 'RECT', x: 'xdate', y: 'count', - expr: ['none', '/', false], + expression: { + source: '/', + inherit: false + }, guide: { x: {label: {text: 'XDATE'}}, y: {label: {text: 'COUNT'}} @@ -573,14 +587,20 @@ type: 'POINT', x: 'xdate', y: 'count', - expr: ['none', '/', true] + expression: { + source: '/', + inherit: true + } }, { type: 'LINE', x: 'xdate', y: 'count', - expr: ['none', '/', true], - transf: [{ + expression: { + source: '/', + inherit: true + }, + transformation: [{ type: 'regression', args: {type: 'loess', x: 'xdate', y: 'count'} }], @@ -594,8 +614,11 @@ x: 'xdate', y: 'count', color: 'annotation1', - expr: ['none', '/', true], - transf: [{ + expression: { + source: '/', + inherit: true + }, + transformation: [{ type: 'x-limit', args: { x: 'xdate', @@ -609,8 +632,11 @@ x: 'xdate', y: 'count', color: 'annotation2', - expr: ['none', '/', true], - transf: [{ + expression: { + source: '/', + inherit: true + }, + transformation: [{ type: 'y-limit', args: { x: 'xdate', @@ -805,7 +831,11 @@ type: 'RECT', x: 'xScale', y: 'yScale', - expr: [false, '$', false], + expression: { + source: '$', + inherit: false, + operator: false + }, frames: [ { key: {x: 2, y: 2}, @@ -817,6 +847,10 @@ x: 'xScale', y: 'yScale', expr: ['none', 'o', false], + expression: { + source: 'o', + inherit: false + }, guide: { showGridLines: '', x: {hide:true}, @@ -828,7 +862,10 @@ proportion: 'piePortion', label: 'label', color: 'label_color', - expr: ['none', 'o', true] + expression: { + source: 'o', + inherit: true + } } ] } @@ -843,7 +880,12 @@ type: 'RECT', x: 'proj', y: 'team', - expr: ['cross', '/', false, 'project', 'team'], + expression: { + operator: 'cross', + source: '/', + inherit: false, + params: ['project', 'team'] + }, guide: { x: {label: {text: 'project'}}, y: {label: {text: 'team'}} @@ -853,7 +895,10 @@ type: 'RECT', x: 'count', y: 'story', - expr: ['none', '/', true], + expression: { + source: '/', + inherit: true + }, guide: { x: {label: {text: 'count'}}, y: {label: {text: 'story'}} @@ -863,7 +908,10 @@ type: 'POINT', x: 'count', y: 'story', - expr: ['none', '/', true] + expression: { + source:'/', + inherit:true + } } ] } @@ -880,7 +928,7 @@ type: 'RECT', x: 'xdate', y: 'counz', - expr: ['none', '@', false], + expression: {source:'@', inherit:false}, guide: { x: {label: {text: 'XDATE'}}, y: {label: {text: 'COUNT'}} @@ -890,14 +938,20 @@ type: 'POINT', x: 'xdate', y: 'counz', - expr: ['none', '@', true] + expression: { + source: '@', + inherit: true + } }, { type: 'LINE', x: 'xdate', y: 'counz', - expr: ['none', '@', true], - transf: [{ + expression: { + source: '@', + inherit: true + }, + transformation: [{ type: 'regression', args: {type: 'loess', x: 'xdate', y: 'count'} }], @@ -911,23 +965,28 @@ x: 'xdate', y: 'counz', color: 'annotation1', - expr: ['none', '@', true], - transf: [{ - type: 'x-limit', - args: { - x: 'xdate', - y: 'count', - limit: 10 + expression: { + source: '@', + inherit: true, + operator: 'none' + }, + transformation: [ + { + type: 'x-limit', + args: {x: 'xdate', y: 'count', limit: 10} } - }] + ] }, { type: 'LINE', x: 'xdate', y: 'counz', color: 'annotation2', - expr: ['none', '@', true], - transf: [{ + expression: { + source: '@', + inherit: true + }, + transformation: [{ type: 'y-limit', args: { x: 'xdate', diff --git a/src/charts/tau.gpl.js b/src/charts/tau.gpl.js index cf30fb095..7e130809f 100644 --- a/src/charts/tau.gpl.js +++ b/src/charts/tau.gpl.js @@ -46,12 +46,12 @@ var FramesAlgebra = { } }; -var calcBaseFrame = (unit, baseFrame) => { +var calcBaseFrame = (unitExpression, baseFrame) => { var tmpFrame = _.pick(baseFrame || {}, 'source', 'pipe'); - var srcAlias = unit.expr[1]; - var bInherit = unit.expr[2]; + var srcAlias = unitExpression.source; + var bInherit = unitExpression.inherit; var ownFrame = {source: srcAlias, pipe: []}; if (bInherit && (ownFrame.source !== tmpFrame.source)) { @@ -126,13 +126,23 @@ export class GPL extends Emitter { var buildRecursively = (root, parentPipe) => { - if (root.expr[0] !== false) { - var expr = this.parseExpression(root.expr, parentPipe); + if (root.expression.operator !== false) { - root.transf = root.transf || []; + var expr = this.parseExpression(root.expression, parentPipe); + + root.transformation = root.transformation || []; root.frames = expr.exec().map((tuple) => { - var pipe = parentPipe.concat([{type: 'where', args: tuple}]).concat(root.transf); + + var pipe = parentPipe + .concat([ + { + type: 'where', + args: tuple + } + ]) + .concat(root.transformation); + var item = { source: expr.source, pipe: pipe @@ -168,7 +178,7 @@ export class GPL extends Emitter { var continueDrawRecursively = (rootConf, rootFrame) => { - var dataFrame = this.datify(calcBaseFrame(rootConf, rootFrame)); + var dataFrame = this.datify(calcBaseFrame(rootConf.expression, rootFrame)); var UnitClass = this.unitSet.get(rootConf.type); @@ -202,24 +212,30 @@ export class GPL extends Emitter { return frame; } - parseExpression(sExpression, parentPipe) { + parseExpression(expr, parentPipe) { - var funcName = sExpression[0]; - var srcAlias = sExpression[1]; - var inheritQ = sExpression[2]; - var funcArgs = sExpression.slice(3); + var funcName = expr.operator || 'none'; + var srcAlias = expr.source; + var bInherit = expr.inherit; + var funcArgs = expr.params; - var dataFn = inheritQ ? + var dataFn = bInherit ? (() => parentPipe.reduce( (data, cfg) => this.trans[cfg.type](data, cfg.args), (this.sources[srcAlias].data))) : (() => this.sources[srcAlias].data); + var func = FramesAlgebra[funcName]; + + if (!func) { + throw new Error(`${funcName} operator is not supported`); + } + return { source : srcAlias, - func : FramesAlgebra[funcName], + func : func, args : funcArgs, - exec : () => FramesAlgebra[funcName](...[dataFn].concat(funcArgs)) + exec : () => func(...[dataFn].concat(funcArgs)) }; } } \ No newline at end of file From f33970aa04bbcea9641ac5c4a257ee8353369c31 Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Mon, 23 Feb 2015 18:41:25 +0300 Subject: [PATCH 08/93] [extract-axes] auto layout is implemented --- examples/gpl.html | 49 ++++++++++++-------------------- src/elements/coords.cartesian.js | 8 ++++++ 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/examples/gpl.html b/examples/gpl.html index ce446b1a8..9593155c2 100644 --- a/examples/gpl.html +++ b/examples/gpl.html @@ -392,13 +392,13 @@ }, scales: { - 'size:default' : {type: 'size', name: 'DefSize', fitToFrame: false, source: '?', mid: 5}, - 'color:default' : {type: 'color', name: 'DefColor', fitToFrame: false, source: '?', brewer: null}, - 'story' : {type: 'ordinal', name: 'S', fitToFrame: false, source: '/', dim: 'story'}, - 'bug' : {type: 'ordinal', name: 'B', fitToFrame: false, source: '/', dim: 'bug'}, - 'count' : {type: 'linear', name: 'C', fitToFrame: false, source: '/', dim: 'count', autoScale: true, min: 0, max: 20}, - 'proj' : {type: 'ordinal', name: 'P', fitToFrame: false, source: '/', dim: 'project'}, - 'team' : {type: 'ordinal', name: 'T', fitToFrame: false, source: '/', dim: 'team'} + 'size:default' : {type: 'size', source: '?', mid: 5}, + 'color:default' : {type: 'color', source: '?', brewer: null}, + 'story' : {type: 'ordinal', source: '/', dim: 'story'}, + 'bug' : {type: 'ordinal', source: '/', dim: 'bug'}, + 'count' : {type: 'linear', source: '/', dim: 'count', autoScale: true, min: 0, max: 20}, + 'proj' : {type: 'ordinal', source: '/', dim: 'project'}, + 'team' : {type: 'ordinal', source: '/', dim: 'team'} }, unit: { @@ -412,8 +412,10 @@ params: ['project', 'team'] }, guide: { - x: {label: {text: 'project'}}, - y: {label: {text: 'team'}} + padding: {l: 60, r: 0, t: 0, b: 60}, + showGridLines: '', + x: {padding: 40, label: {text: 'project'}, cssClass:'x axis facet-axis'}, + y: {padding: 40, label: {text: 'team'}, cssClass:'y axis facet-axis'} }, unit: [ { @@ -425,8 +427,10 @@ inherit: true }, guide: { - x: {label: {text: 'count'}}, - y: {label: {text: 'story'}} + autoLayout: 'extract-axes', + padding: {l: 10, r: 10, t: 10, b: 10}, + x: {padding: 10, label: {text: 'count'}}, + y: {padding: 10, label: {text: 'story'}} }, unit: [ { @@ -600,10 +604,7 @@ source: '/', inherit: true }, - transformation: [{ - type: 'regression', - args: {type: 'loess', x: 'xdate', y: 'count'} - }], + transformation: [{type: 'regression', args: {type: 'loess', x: 'xdate', y: 'count'}}], guide: { interpolate: 'basis', cssClass: 'graphical-report__trendline' @@ -618,14 +619,7 @@ source: '/', inherit: true }, - transformation: [{ - type: 'x-limit', - args: { - x: 'xdate', - y: 'count', - limit: 10 - } - }] + transformation: [{type: 'x-limit', args: {x: 'xdate', y: 'count', limit: 10}}] }, { type: 'LINE', @@ -636,14 +630,7 @@ source: '/', inherit: true }, - transformation: [{ - type: 'y-limit', - args: { - x: 'xdate', - y: 'count', - limit: -25 - } - }] + transformation: [{type: 'y-limit', args: {x: 'xdate', y: 'count', limit: -25}}] } ] } diff --git a/src/elements/coords.cartesian.js b/src/elements/coords.cartesian.js index 5ab46f5bc..d1567740e 100644 --- a/src/elements/coords.cartesian.js +++ b/src/elements/coords.cartesian.js @@ -67,6 +67,14 @@ export class Cartesian { textAnchor: 'middle' } ); + + var unit = this.config; + if (unit.guide.autoLayout === 'extract-axes') { + var containerBox = unit.options.container.node().getBBox(); + var guide = unit.guide = unit.guide || {}; + guide.x.hide = ((unit.options.top + unit.options.height) < containerBox.height); + guide.y.hide = ((unit.options.left > 0)); + } } drawLayout(fnCreateScale) { From f1fc934a3cfdd1c87e4e9117aebb409086da0ef4 Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Mon, 23 Feb 2015 18:47:04 +0300 Subject: [PATCH 09/93] jshint errors are fixed --- src/charts/tau.gpl.js | 2 +- src/scales-factory.js | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/charts/tau.gpl.js b/src/charts/tau.gpl.js index 7e130809f..4cee921ca 100644 --- a/src/charts/tau.gpl.js +++ b/src/charts/tau.gpl.js @@ -235,7 +235,7 @@ export class GPL extends Emitter { source : srcAlias, func : func, args : funcArgs, - exec : () => func(...[dataFn].concat(funcArgs)) + exec : () => func.apply(null, [dataFn].concat(funcArgs)) }; } } \ No newline at end of file diff --git a/src/scales-factory.js b/src/scales-factory.js index 4ac988ec5..4da48697d 100644 --- a/src/scales-factory.js +++ b/src/scales-factory.js @@ -162,15 +162,13 @@ var scalesStrategies = { var d3Scale = d3Domain.rangeRound(interval, 1); var scale = (int) => { var x = int; - if (x > max) {x = max} - if (x < min) {x = min} + if (x > max) x = max; + if (x < min) x = min; return d3Scale(x); }; // have to copy properties since d3 produce Function with methods - Object.keys(d3Scale).forEach(function (p) { - return scale[p] = d3Scale[p]; - }); + Object.keys(d3Scale).forEach((p) => (scale[p] = d3Scale[p])); scale.dim = props.dim; scale.domain = () => varSet; From be067d3a35da88bdaba8c91e1addc2133c61a988 Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Tue, 24 Feb 2015 15:14:26 +0300 Subject: [PATCH 10/93] Sample streaming on tauCharts architecture --- examples/streaming.html | 291 +++++++++++++++++++++++ src/charts/tau.gpl.js | 45 ++-- src/elements/coords.cartesian.js | 396 ++++++++++++++++++++++++++++++- src/elements/element.point.js | 8 +- 4 files changed, 710 insertions(+), 30 deletions(-) create mode 100644 examples/streaming.html diff --git a/examples/streaming.html b/examples/streaming.html new file mode 100644 index 000000000..c87a8c0dc --- /dev/null +++ b/examples/streaming.html @@ -0,0 +1,291 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + \ No newline at end of file diff --git a/src/charts/tau.gpl.js b/src/charts/tau.gpl.js index 4cee921ca..8148e3043 100644 --- a/src/charts/tau.gpl.js +++ b/src/charts/tau.gpl.js @@ -8,7 +8,7 @@ import {CSS_PREFIX} from '../const'; var FramesAlgebra = { - 'cross': function(dataFn, dimX, dimY) { + 'cross': function (dataFn, dimX, dimY) { var convert = (v) => (v instanceof Date) ? v.getTime() : v; @@ -41,7 +41,7 @@ var FramesAlgebra = { []); }, - 'none': function(datus, dimX, dimY, pipe) { + 'none': function (datus, dimX, dimY, pipe) { return [null]; } }; @@ -100,7 +100,7 @@ export class GPL extends Emitter { var containerNode = this._layout.content; var container = d3.select(this._layout.content); - containerNode.innerHTML = ''; + //containerNode.innerHTML = ''; var size = _.clone(xSize) || {}; if (!size.width || !size.height) { @@ -111,12 +111,19 @@ export class GPL extends Emitter { // expand units structure this.root = this.expandUnitsStructure(this.config.unit); + container.selectAll('svg') + .data(['const']) + .enter() + .append('svg') + .attr(_.extend({'class': `${CSS_PREFIX}svg`}, size)); + + this.root.options = { - container: container.append('svg').attr(_.extend({'class': `${CSS_PREFIX}svg`}, size)), - left : 0, - top : 0, - width : size.width, - height : size.height + container: d3.select(container.selectAll('svg').node()), + left: 0, + top: 0, + width: size.width, + height: size.height }; this.drawUnitsStructure(this.root); @@ -176,11 +183,13 @@ export class GPL extends Emitter { drawUnitsStructure() { + var self = this; + var continueDrawRecursively = (rootConf, rootFrame) => { - var dataFrame = this.datify(calcBaseFrame(rootConf.expression, rootFrame)); + var dataFrame = self.datify(calcBaseFrame(rootConf.expression, rootFrame)); - var UnitClass = this.unitSet.get(rootConf.type); + var UnitClass = self.unitSet.get(rootConf.type); var unitNode = new UnitClass(rootConf); @@ -194,14 +203,16 @@ export class GPL extends Emitter { // - shape var name = alias ? alias : `${type}:default`; - return this.scalesCreator.create(this.scales[name], dataFrame, settings); + name = name.scaleDim || name; + + return self.scalesCreator.create(self.scales[name], dataFrame, settings); }) - .drawFrames(rootConf.frames.map(this.datify.bind(this)), continueDrawRecursively); + .drawFrames(rootConf.frames.map(self.datify.bind(self)), continueDrawRecursively); return rootConf; }; - return continueDrawRecursively(this.root); + return continueDrawRecursively(self.root); } datify(frame) { @@ -232,10 +243,10 @@ export class GPL extends Emitter { } return { - source : srcAlias, - func : func, - args : funcArgs, - exec : () => func.apply(null, [dataFn].concat(funcArgs)) + source: srcAlias, + func: func, + args: funcArgs, + exec: () => func.apply(null, [dataFn].concat(funcArgs)) }; } } \ No newline at end of file diff --git a/src/elements/coords.cartesian.js b/src/elements/coords.cartesian.js index d1567740e..df563acc3 100644 --- a/src/elements/coords.cartesian.js +++ b/src/elements/coords.cartesian.js @@ -1,7 +1,270 @@ import {utilsDraw} from '../utils/utils-draw'; import {CSS_PREFIX} from '../const'; import {utils} from '../utils/utils'; -import {TMatrix} from '../matrix'; +import {FormatterRegistry} from '../formatter-registry'; +import * as _ from 'underscore'; +import * as d3 from 'd3'; + + +var translate = (left, top) => 'translate(' + left + ',' + top + ')'; +var rotate = (angle) => 'rotate(' + angle + ')'; +var getOrientation = (scaleOrient) => _.contains(['bottom', 'top'], scaleOrient.toLowerCase()) ? 'h' : 'v'; + +var d3getComputedTextLength = _.memoize( + (d3Text) => d3Text.node().getComputedTextLength(), + (d3Text) => d3Text.node().textContent.length); + +var cutText = (textString, widthLimit, getComputedTextLength) => { + + getComputedTextLength = getComputedTextLength || d3getComputedTextLength; + + textString.each(function () { + var textD3 = d3.select(this); + var tokens = textD3.text().split(/\s+/); + + var stop = false; + var parts = tokens.reduce((memo, t, i) => { + + if (stop) { + return memo; + } + + var text = (i > 0) ? [memo, t].join(' ') : t; + var len = getComputedTextLength(textD3.text(text)); + if (len < widthLimit) { + memo = text; + } + else { + var available = Math.floor(widthLimit / len * text.length); + memo = text.substr(0, available - 4) + '...'; + stop = true; + } + + return memo; + + }, ''); + + textD3.text(parts); + }); +}; + +var wrapText = (textNode, widthLimit, linesLimit, tickLabelFontHeight, isY, getComputedTextLength) => { + + getComputedTextLength = getComputedTextLength || d3getComputedTextLength; + + var addLine = (targetD3, text, lineHeight, x, y, dy, lineNumber) => { + var dyNew = (lineNumber * lineHeight) + dy; + return targetD3 + .append('tspan') + .attr('x', x) + .attr('y', y) + .attr('dy', dyNew + 'em') + .text(text); + }; + + textNode.each(function () { + var textD3 = d3.select(this), + tokens = textD3.text().split(/\s+/), + lineHeight = 1.1, // ems + x = textD3.attr('x'), + y = textD3.attr('y'), + dy = parseFloat(textD3.attr('dy')); + + textD3.text(null); + var tempSpan = addLine(textD3, null, lineHeight, x, y, dy, 0); + + var stopReduce = false; + var tokensCount = (tokens.length - 1); + var lines = tokens + .reduce((memo, next, i) => { + + if (stopReduce) { + return memo; + } + + var isLimit = (memo.length === linesLimit) || (i === tokensCount); + var last = memo[memo.length - 1]; + var text = (last !== '') ? (last + ' ' + next) : next; + var tLen = getComputedTextLength(tempSpan.text(text)); + var over = tLen > widthLimit; + + if (over && isLimit) { + var available = Math.floor(widthLimit / tLen * text.length); + memo[memo.length - 1] = text.substr(0, available - 4) + '...'; + stopReduce = true; + } + + if (over && !isLimit) { + memo.push(next); + } + + if (!over) { + memo[memo.length - 1] = text; + } + + return memo; + + }, ['']) + .filter((l) => l.length > 0); + + y = isY ? (-1 * (lines.length - 1) * Math.floor(tickLabelFontHeight * 0.5)) : y; + lines.forEach((text, i) => addLine(textD3, text, lineHeight, x, y, dy, i)); + + tempSpan.remove(); + }); +}; + +var decorateAxisTicks = (nodeScale, x, size) => { + + var selection = nodeScale.selectAll('.tick line'); + + var sectorSize = size / selection[0].length; + var offsetSize = sectorSize / 2; + + var isHorizontal = ('h' === getOrientation(x.guide.scaleOrient)); + + if (x.scaleType === 'ordinal' || x.scaleType === 'period') { + + var key = (isHorizontal) ? 'x' : 'y'; + var val = (isHorizontal) ? offsetSize : (-offsetSize); + + selection.attr(key + '1', val).attr(key + '2', val); + } +}; + +var fixAxisTickOverflow = (nodeScale, x) => { + + var isHorizontal = ('h' === getOrientation(x.guide.scaleOrient)); + + if (isHorizontal && (x.scaleType === 'time')) { + var timeTicks = nodeScale.selectAll('.tick')[0]; + if (timeTicks.length < 2) { + return; + } + + var tick0 = parseFloat(timeTicks[0].attributes.transform.value.replace('translate(', '')); + var tick1 = parseFloat(timeTicks[1].attributes.transform.value.replace('translate(', '')); + + var tickStep = tick1 - tick0; + + var maxTextLn = 0; + var iMaxTexts = -1; + var timeTexts = nodeScale.selectAll('.tick text')[0]; + timeTexts.forEach((textNode, i) => { + var innerHTML = textNode.textContent || ''; + var textLength = innerHTML.length; + if (textLength > maxTextLn) { + maxTextLn = textLength; + iMaxTexts = i; + } + }); + + if (iMaxTexts >= 0) { + var rect = timeTexts[iMaxTexts].getBoundingClientRect(); + // 2px from each side + if ((tickStep - rect.width) < 8) { + nodeScale.classed({'graphical-report__d3-time-overflown': true}); + } + } + } +}; + +var fixAxisBottomLine = (nodeScale, x, size) => { + + var selection = nodeScale.selectAll('.tick line'); + + var isHorizontal = ('h' === getOrientation(x.guide.scaleOrient)); + + if (isHorizontal) { + return; + } + + var doApply = false; + var tickOffset = -1; + + if (x.scaleType === 'time') { + doApply = true; + tickOffset = 0; + } + else if (x.scaleType === 'ordinal' || x.scaleType === 'period') { + doApply = true; + var sectorSize = size / selection[0].length; + var offsetSize = sectorSize / 2; + tickOffset = (-offsetSize); + } + + if (doApply) { + var tickGroupClone = nodeScale.select('.tick').node().cloneNode(true); + nodeScale + .append(() => tickGroupClone) + .attr('transform', translate(0, size - tickOffset)); + } +}; + +var decorateAxisLabel = (nodeScale, x) => { + var orient = getOrientation(x.guide.scaleOrient); + var koeff = ('h' === orient) ? 1 : -1; + var labelTextNode = nodeScale + .append('text') + .attr('transform', rotate(x.guide.label.rotate)) + .attr('class', x.guide.label.cssClass) + .attr('x', koeff * x.guide.size * 0.5) + .attr('y', koeff * x.guide.label.padding) + .style('text-anchor', x.guide.label.textAnchor); + + var delimiter = ' > '; + var tags = x.guide.label.text.split(delimiter); + var tLen = tags.length; + tags.forEach((token, i) => { + + labelTextNode + .append('tspan') + .attr('class', 'label-token label-token-' + i) + .text(token); + + if (i < (tLen - 1)) { + labelTextNode + .append('tspan') + .attr('class', 'label-token-delimiter label-token-delimiter-' + i) + .text(delimiter); + } + }); + + if (x.guide.label.dock === 'right') { + let box = nodeScale.selectAll('path.domain').node().getBBox(); + labelTextNode.attr('x', (orient === 'h') ? (box.width) : 0); + } + else if (x.guide.label.dock === 'left') { + let box = nodeScale.selectAll('path.domain').node().getBBox(); + labelTextNode.attr('x', (orient === 'h') ? 0 : (-box.height)); + } +}; + +var decorateTickLabel = (nodeScale, x) => { + + var isHorizontal = ('h' === getOrientation(x.guide.scaleOrient)); + + var angle = x.guide.rotate; + + var ticks = nodeScale.selectAll('.tick text'); + ticks + .attr('transform', rotate(angle)) + .style('text-anchor', x.guide.textAnchor); + + if (angle === 90) { + var dy = parseFloat(ticks.attr('dy')) / 2; + ticks.attr('x', 9).attr('y', 0).attr('dy', `${dy}em`); + } + + if (x.guide.tickFormatWordWrap) { + ticks + .call(wrapText, x.guide.tickFormatWordWrapLimit, x.guide.tickFormatWordWrapLines, x.guide.$maxTickTextH, !isHorizontal); + } else { + ticks + .call(cutText, x.guide.tickFormatWordWrapLimit); + } +}; + export class Cartesian { @@ -108,22 +371,29 @@ export class Cartesian { var X_AXIS_POS = [0, H + node.guide.x.padding]; var Y_AXIS_POS = [0 - node.guide.y.padding, 0]; - var container = options - .container + var s = `${parseInt(T)}_${parseInt(L)}`; + var classes = `${CSS_PREFIX}cell cell cell_${s}`; + var selector = `.cell_${s}`; + + options + .container.selectAll(selector) + .data([{L,T,W,H}]) + .enter() .append('g') - .attr('class', CSS_PREFIX + 'cell ' + 'cell') - .attr('transform', utilsDraw.translate(L, T)) - .datum({'$where': node.$where}); + .attr('class', classes) + .transition() + .duration(750) + .attr('transform', utilsDraw.translate(L, T)); if (!node.x.guide.hide) { - utilsDraw.fnDrawDimAxis.call(container, node.x, X_AXIS_POS, W); + this._fnDrawDimAxis(options.container.selectAll(selector), node.x, X_AXIS_POS, W, s + 'x'); } if (!node.y.guide.hide) { - utilsDraw.fnDrawDimAxis.call(container, node.y, Y_AXIS_POS, H); + this._fnDrawDimAxis(options.container.selectAll(selector), node.y, Y_AXIS_POS, H, s + 'y'); } - this.grid = utilsDraw.fnDrawGrid.call(container, node, H, W); + this.grid = this._fnDrawGrid(options.container.selectAll(selector), node, H, W, s); this.W = W; this.H = H; @@ -176,4 +446,112 @@ export class Cartesian { }, []); } + + _fnDrawDimAxis(container, x, AXIS_POSITION, size, S) { + + if (x.scaleDim) { + + var axisScale = d3.svg + .axis() + .scale(x.scaleObj) + .orient(x.guide.scaleOrient); + + var formatter = FormatterRegistry.get(x.guide.tickFormat, x.guide.tickFormatNullAlias); + if (formatter !== null) { + axisScale.ticks(Math.round(size / x.guide.density)); + axisScale.tickFormat(formatter); + } + + container.selectAll('.axis_' + S) + .data([S]) + .enter() + .append('g') + .attr('class', x.guide.cssClass + ' axis_' + S) + .attr('transform', utilsDraw.translate(...AXIS_POSITION)) + .call(function (selection) { + if (!selection.empty()) { + axisScale.call(this, selection); + decorateAxisTicks(selection, x, size); + decorateTickLabel(selection, x); + decorateAxisLabel(selection, x); + fixAxisTickOverflow(selection, x); + } + }); + } + } + + _fnDrawGrid(container, node, H, W, S) { + + container + .selectAll('.grid_' + S) + .data([S]) + .enter() + .append('g') + .attr('class', 'grid grid_' + S) + .attr('transform', translate(0, 0)); + + var grid = container.select('.grid_' + S); + + var linesOptions = (node.guide.showGridLines || '').toLowerCase(); + if (linesOptions.length > 0) { + + var gridLines = grid.append('g').attr('class', 'grid-lines'); + + if ((linesOptions.indexOf('x') > -1) && node.x.scaleDim) { + var x = node.x; + var xGridAxis = d3.svg + .axis() + .scale(x.scaleObj) + .orient(x.guide.scaleOrient) + .tickSize(H); + + let formatter = FormatterRegistry.get(x.guide.tickFormat); + if (formatter !== null) { + xGridAxis.ticks(Math.round(W / x.guide.density)); + xGridAxis.tickFormat(formatter); + } + + var xGridLines = gridLines.append('g').attr('class', 'grid-lines-x').call(xGridAxis); + + decorateAxisTicks(xGridLines, x, W); + + var firstXGridLine = xGridLines.select('g.tick'); + if (firstXGridLine.node() && firstXGridLine.attr('transform') !== 'translate(0,0)') { + var zeroNode = firstXGridLine.node().cloneNode(true); + gridLines.node().appendChild(zeroNode); + d3.select(zeroNode) + .attr('class', 'border') + .attr('transform', translate(0, 0)) + .select('line') + .attr('x1', 0) + .attr('x2', 0); + } + } + + if ((linesOptions.indexOf('y') > -1) && node.y.scaleDim) { + var y = node.y; + var yGridAxis = d3.svg + .axis() + .scale(y.scaleObj) + .orient(y.guide.scaleOrient) + .tickSize(-W); + + let formatter = FormatterRegistry.get(y.guide.tickFormat); + if (formatter !== null) { + yGridAxis.ticks(Math.round(H / y.guide.density)); + yGridAxis.tickFormat(formatter); + } + + var yGridLines = gridLines.append('g').attr('class', 'grid-lines-y').call(yGridAxis); + + decorateAxisTicks(yGridLines, y, H); + fixAxisBottomLine(yGridLines, y, H); + } + + // TODO: make own axes and grid instead of using d3's in such tricky way + gridLines.selectAll('text').remove(); + } + + return grid; + } } \ No newline at end of file diff --git a/src/elements/element.point.js b/src/elements/element.point.js index 46d5c047c..52c49a88d 100644 --- a/src/elements/element.point.js +++ b/src/elements/element.point.js @@ -32,19 +32,19 @@ export class Point { var update = function () { var props = { - 'r' : (d) => sScale(d[sScale.dim]), + 'r' : 0, 'cx' : (d) => xScale(d[xScale.dim]), 'cy' : (d) => yScale(d[yScale.dim]), 'class' : (d) => `${CSS_PREFIX}dot dot i-role-element i-role-datum ${cScale(d[cScale.dim])}` }; - return this.attr(props); + return this.attr(props).transition().duration(500).attr('r', (d) => sScale(d[sScale.dim])); }; frames.map((frame) => { var elements; - elements = canvas.selectAll('.dot' + parseInt(Math.random() * 1000)).data(frame.take()); - elements.call(update); + elements = canvas.selectAll('.dot').data(frame.take()); +// elements.call(update); elements.exit().remove(); elements.enter().append('circle').call(update); }); From dd7778f4312a6759a01654e0e3208add6397404a Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Tue, 24 Feb 2015 16:03:39 +0300 Subject: [PATCH 11/93] Fix code review issues --- src/charts/tau.gpl.js | 116 +++++++++++++------------------ src/elements/coords.cartesian.js | 39 +++++------ 2 files changed, 68 insertions(+), 87 deletions(-) diff --git a/src/charts/tau.gpl.js b/src/charts/tau.gpl.js index 8148e3043..ab3348290 100644 --- a/src/charts/tau.gpl.js +++ b/src/charts/tau.gpl.js @@ -126,96 +126,81 @@ export class GPL extends Emitter { height: size.height }; - this.drawUnitsStructure(this.root); + this._drawUnitsStructure(this.root); } - expandUnitsStructure(rootUnit) { + expandUnitsStructure(root, parentPipe = []) { - var buildRecursively = (root, parentPipe) => { + if (root.expression.operator !== false) { - if (root.expression.operator !== false) { + var expr = this._parseExpression(root.expression, parentPipe); - var expr = this.parseExpression(root.expression, parentPipe); + root.transformation = root.transformation || []; - root.transformation = root.transformation || []; + root.frames = expr.exec().map((tuple) => { - root.frames = expr.exec().map((tuple) => { + var pipe = parentPipe + .concat([ + { + type: 'where', + args: tuple + } + ]) + .concat(root.transformation); - var pipe = parentPipe - .concat([ - { - type: 'where', - args: tuple - } - ]) - .concat(root.transformation); + var item = { + source: expr.source, + pipe: pipe + }; - var item = { - source: expr.source, - pipe: pipe - }; + if (tuple) { + item.key = tuple; + } - if (tuple) { - item.key = tuple; - } - - item.unit = (root.unit) ? root.unit.map((unit) => utils.clone(unit)) : []; - - return item; - }); - } - - root.frames.map((item) => { - // key: tuple, - // source: expr.source, - // pipe: pipe - - item.unit.map((unit) => buildRecursively(unit, item.pipe)); + item.unit = (root.unit) ? root.unit.map((unit) => utils.clone(unit)) : []; return item; }); + } - return root; - }; + root.frames.map((item) => { + // key: tuple, + // source: expr.source, + // pipe: pipe - return buildRecursively(rootUnit, []); - } + item.unit.map((unit) => this.expandUnitsStructure(unit, item.pipe)); - drawUnitsStructure() { + return item; + }); - var self = this; + return root; + } - var continueDrawRecursively = (rootConf, rootFrame) => { + _drawUnitsStructure(rootConf, rootFrame = null) { - var dataFrame = self.datify(calcBaseFrame(rootConf.expression, rootFrame)); + var self = this; - var UnitClass = self.unitSet.get(rootConf.type); + var dataFrame = self._datify(calcBaseFrame(rootConf.expression, rootFrame)); - var unitNode = new UnitClass(rootConf); + var UnitClass = self.unitSet.get(rootConf.type); - unitNode - .drawLayout((type, alias, settings) => { + var unitNode = new UnitClass(rootConf); - // type is one of: - // - pos - // - size - // - color - // - shape - var name = alias ? alias : `${type}:default`; + unitNode + .drawLayout((type, alias, settings) => { - name = name.scaleDim || name; + var name = alias ? alias : `${type}:default`; - return self.scalesCreator.create(self.scales[name], dataFrame, settings); - }) - .drawFrames(rootConf.frames.map(self.datify.bind(self)), continueDrawRecursively); + name = name.scaleDim || name; - return rootConf; - }; + return self.scalesCreator.create(self.scales[name], dataFrame, settings); + }) + .drawFrames(rootConf.frames.map(self._datify.bind(self)), self._drawUnitsStructure.bind(self)); - return continueDrawRecursively(self.root); + return rootConf; } - datify(frame) { + _datify(frame) { var data = this.sources[frame.source].data; var trans = this.trans; frame.take = () => frame.pipe.reduce((data, pipeCfg) => trans[pipeCfg.type](data, pipeCfg.args), data); @@ -223,18 +208,17 @@ export class GPL extends Emitter { return frame; } - parseExpression(expr, parentPipe) { + _parseExpression(expr, parentPipe) { var funcName = expr.operator || 'none'; var srcAlias = expr.source; var bInherit = expr.inherit; var funcArgs = expr.params; + var src = this.sources[srcAlias]; var dataFn = bInherit ? - (() => parentPipe.reduce( - (data, cfg) => this.trans[cfg.type](data, cfg.args), - (this.sources[srcAlias].data))) : - (() => this.sources[srcAlias].data); + (() => parentPipe.reduce((data, cfg) => this.trans[cfg.type](data, cfg.args), src.data)) : + (() => src.data); var func = FramesAlgebra[funcName]; diff --git a/src/elements/coords.cartesian.js b/src/elements/coords.cartesian.js index df563acc3..afe4e8986 100644 --- a/src/elements/coords.cartesian.js +++ b/src/elements/coords.cartesian.js @@ -347,14 +347,14 @@ export class Cartesian { var options = node.options; var padding = node.guide.padding; - var L = options.left + padding.l; - var T = options.top + padding.t; + var innerLeft = options.left + padding.l; + var innerTop = options.top + padding.t; - var W = options.width - (padding.l + padding.r); - var H = options.height - (padding.t + padding.b); + var innerWidth = options.width - (padding.l + padding.r); + var innerHeight = options.height - (padding.t + padding.b); - this.x = this.xScale = fnCreateScale('pos', node.x, [0, W]); - this.y = this.yScale = fnCreateScale('pos', node.y, [H, 0]); + this.x = this.xScale = fnCreateScale('pos', node.x, [0, innerWidth]); + this.y = this.yScale = fnCreateScale('pos', node.y, [innerHeight, 0]); node.x = this.xScale; node.y = this.yScale; @@ -365,37 +365,34 @@ export class Cartesian { node.x.guide = node.guide.x; node.y.guide = node.guide.y; - node.x.guide.size = W; - node.y.guide.size = H; + node.x.guide.size = innerWidth; + node.y.guide.size = innerHeight; - var X_AXIS_POS = [0, H + node.guide.x.padding]; - var Y_AXIS_POS = [0 - node.guide.y.padding, 0]; - - var s = `${parseInt(T)}_${parseInt(L)}`; + var s = `${parseInt(innerTop)}_${parseInt(innerLeft)}`; var classes = `${CSS_PREFIX}cell cell cell_${s}`; var selector = `.cell_${s}`; options .container.selectAll(selector) - .data([{L,T,W,H}]) + .data([{innerLeft,innerTop,innerWidth,innerHeight}]) .enter() .append('g') .attr('class', classes) .transition() .duration(750) - .attr('transform', utilsDraw.translate(L, T)); + .attr('transform', utilsDraw.translate(innerLeft, innerTop)); if (!node.x.guide.hide) { - this._fnDrawDimAxis(options.container.selectAll(selector), node.x, X_AXIS_POS, W, s + 'x'); + this._fnDrawDimAxis(options.container.selectAll(selector), node.x, [0, innerHeight + node.guide.x.padding], innerWidth, s + 'x'); } if (!node.y.guide.hide) { - this._fnDrawDimAxis(options.container.selectAll(selector), node.y, Y_AXIS_POS, H, s + 'y'); + this._fnDrawDimAxis(options.container.selectAll(selector), node.y, [0 - node.guide.y.padding, 0], innerHeight, s + 'y'); } - this.grid = this._fnDrawGrid(options.container.selectAll(selector), node, H, W, s); - this.W = W; - this.H = H; + this.grid = this._fnDrawGrid(options.container.selectAll(selector), node, innerHeight, innerWidth, s); + this.W = innerWidth; + this.H = innerHeight; return this; } @@ -404,7 +401,7 @@ export class Cartesian { var self = this; return frames.reduce( - (memo, frame) => { + (units, frame) => { var mapper; if (frame.key) { var coordX = self.x(frame.key[self.x.dim]); @@ -442,7 +439,7 @@ export class Cartesian { frame.unit.map((u) => continuation(mapper(u), frame)); - return memo.concat(frame.unit.map(mapper)); + return units.concat(frame.unit.map(mapper)); }, []); } From c03996eca2b9fa5ace00fdcd1fc1975b75ffe103 Mon Sep 17 00:00:00 2001 From: konstantin Date: Tue, 24 Feb 2015 16:45:21 +0300 Subject: [PATCH 12/93] update build for debug --- Gruntfile.js | 33 +++++++++++++++++++++++++++++--- examples/streaming.html | 25 ++---------------------- package.json | 9 +++++++-- src/elements/coords.cartesian.js | 4 ++-- src/formatter-registry.js | 2 +- src/scales-factory.js | 4 ++-- src/size.js | 2 +- src/unit-domain-mixin.js | 4 ++-- src/utils/utils-draw.js | 4 ++-- tasks/compile.js | 2 +- 10 files changed, 50 insertions(+), 39 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 5cd48af7c..f0a60dee9 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,5 +1,6 @@ /*global module:false*/ var autoprefixer = require('autoprefixer-core'); +var webpack = require('webpack'); module.exports = function (grunt) { // Project configuration. @@ -74,9 +75,7 @@ module.exports = function (grunt) { unit: { reporters: ["dots", "coverage"], preprocessors: {"tau_modules/**/*.js": "coverage", "plugins/*.js": "coverage"}, - coverageReporter: { - - } + coverageReporter: {} } }, uglify: { @@ -266,6 +265,33 @@ module.exports = function (grunt) { files: ['less/*.less'], tasks: ['less'] } + }, + 'webpack-dev-server': { + options: { + webpack: { // webpack options + entry: "./src/tau.newCharts.js", + output: { + // libraryTarget: "amd", + library:'tauCharts', + path: "build/", + filename: "build.js" + }, + module: { + loaders: [ + {test: /\.js$/, exclude: /node_modules/, loader: "babel-loader"} + ] + } + }, + publicPath: "/" + }, + start: { + port:9000, + keepAlive: true, + webpack: { + devtool: "sourcemap", + debug: true + } + } } }); // load local tasks @@ -286,6 +312,7 @@ module.exports = function (grunt) { grunt.loadNpmTasks('grunt-contrib-rename'); grunt.loadNpmTasks('grunt-postcss'); grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-webpack'); // Default task. //grunt.registerTask('default', ['jshint', 'qunit', 'concat', 'uglify']); grunt.registerTask('default', ['bowercopy', 'less', 'compile:dev', 'jshint', 'watch:js']); diff --git a/examples/streaming.html b/examples/streaming.html index c87a8c0dc..0d2721ded 100644 --- a/examples/streaming.html +++ b/examples/streaming.html @@ -95,29 +95,8 @@
+ diff --git a/package.json b/package.json index 611e91594..36f643ec5 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,11 @@ "url": "https://github.com/TargetProcess/tauCharts.git" }, "devDependencies": { - "6to5": "3.0.9", "almond": "0.3.0", "autoprefixer-core": "^5.0.0", + "babel": "^4.4.6", + "babel-core": "^4.4.6", + "babel-loader": "^4.0.0", "chai": "1.10.0", "grunt": "~0.4.5", "grunt-bowercopy": "^1.2.0", @@ -45,6 +47,7 @@ "grunt-karma": "0.10.1", "grunt-postcss": "^0.3.0", "grunt-shell": "", + "grunt-webpack": "^1.0.8", "karma": "~0.12.31", "karma-chrome-launcher": "~0.1.7", "karma-cli": "0.0.4", @@ -54,7 +57,9 @@ "karma-phantomjs-launcher": "~0.1.4", "karma-requirejs": "0.2.2", "requirejs": "2.1.15", - "requirejs-text": "^2.0.12" + "requirejs-text": "^2.0.12", + "webpack": "^1.6.0", + "webpack-dev-server": "^1.7.0" }, "scripts": { "test": "./node_modules/.bin/karma start config/karma.conf.js", diff --git a/src/elements/coords.cartesian.js b/src/elements/coords.cartesian.js index afe4e8986..d0f5b4fe6 100644 --- a/src/elements/coords.cartesian.js +++ b/src/elements/coords.cartesian.js @@ -2,8 +2,8 @@ import {utilsDraw} from '../utils/utils-draw'; import {CSS_PREFIX} from '../const'; import {utils} from '../utils/utils'; import {FormatterRegistry} from '../formatter-registry'; -import * as _ from 'underscore'; -import * as d3 from 'd3'; +import {default as _} from 'underscore'; +import {default as d3} from 'd3'; var translate = (left, top) => 'translate(' + left + ',' + top + ')'; diff --git a/src/formatter-registry.js b/src/formatter-registry.js index bf8a75a43..315034d83 100644 --- a/src/formatter-registry.js +++ b/src/formatter-registry.js @@ -1,5 +1,5 @@ /* jshint ignore:start */ -import * as d3 from 'd3'; +import {default as d3} from 'd3'; /* jshint ignore:end */ var FORMATS_MAP = { diff --git a/src/scales-factory.js b/src/scales-factory.js index 4da48697d..b3ce1179a 100644 --- a/src/scales-factory.js +++ b/src/scales-factory.js @@ -1,8 +1,8 @@ import {UnitDomainPeriodGenerator} from './unit-domain-period-generator'; import {utils} from './utils/utils'; /* jshint ignore:start */ -import * as _ from 'underscore'; -import * as d3 from 'd3'; +import {default as _} from 'underscore'; +import {default as d3} from 'd3'; /* jshint ignore:end */ var scalesStrategies = { diff --git a/src/size.js b/src/size.js index 8c5ddf71e..d6faa775f 100644 --- a/src/size.js +++ b/src/size.js @@ -37,5 +37,5 @@ var sizeScale = function (srcValues, minSize, maxSize, normalSize) { return (minSize + (f(posX) * k)); }; }; - + var s = 23; export {sizeScale}; \ No newline at end of file diff --git a/src/unit-domain-mixin.js b/src/unit-domain-mixin.js index 6b88ec98b..69f2820a6 100644 --- a/src/unit-domain-mixin.js +++ b/src/unit-domain-mixin.js @@ -2,8 +2,8 @@ import {UnitDomainPeriodGenerator} from './unit-domain-period-generator'; import {utils} from './utils/utils'; import {sizeScale} from './size'; /* jshint ignore:start */ -import * as _ from 'underscore'; -import * as d3 from 'd3'; +import {default as _} from 'underscore'; +import {default as d3} from 'd3'; /* jshint ignore:end */ var autoScaleMethods = { diff --git a/src/utils/utils-draw.js b/src/utils/utils-draw.js index 5e5e8ec1d..513400965 100644 --- a/src/utils/utils-draw.js +++ b/src/utils/utils-draw.js @@ -1,8 +1,8 @@ import {utils} from '../utils/utils'; import {FormatterRegistry} from '../formatter-registry'; /* jshint ignore:start */ -import * as _ from 'underscore'; -import * as d3 from 'd3'; +import {default as _} from 'underscore'; +import {default as d3} from 'd3'; /* jshint ignore:end */ var translate = (left, top) => 'translate(' + left + ',' + top + ')'; diff --git a/tasks/compile.js b/tasks/compile.js index c8b457f42..4dc862125 100644 --- a/tasks/compile.js +++ b/tasks/compile.js @@ -1,5 +1,5 @@ var requirejs = require('requirejs'); -var to5 = require('6to5'); +var to5 = require("babel"); var LOG_LEVEL_TRACE = 0, LOG_LEVEL_WARN = 2; requirejs.define('node/print', [], function () { return function print(msg) { From 0d30c9c587d6d9f92bb6ca8eb17875fbbdfa391c Mon Sep 17 00:00:00 2001 From: konstantin Date: Tue, 24 Feb 2015 18:05:42 +0300 Subject: [PATCH 13/93] Code cleanup --- Gruntfile.js | 14 +++----------- config.rb | 19 ------------------- src/{tau.newCharts.js => tau.charts.js} | 0 tasks/compile.js | 4 ++-- 4 files changed, 5 insertions(+), 32 deletions(-) delete mode 100644 config.rb rename src/{tau.newCharts.js => tau.charts.js} (100%) diff --git a/Gruntfile.js b/Gruntfile.js index f0a60dee9..4e0e2377d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -7,16 +7,7 @@ module.exports = function (grunt) { var src = [ "*.js", "**/*.js", - "!charts/bar.js", - '!tau.plugins.js', - '!tau.svg.js', - '!charts/line.js', - '!charts/scatterplot.js', - '!addons/*.js', - '!tau.charts.js', - '!class.js', - '!tau.data.js', - '!tau.data.types.js' + '!addons/*.js' ]; grunt.initConfig({ // Metadata. @@ -269,7 +260,8 @@ module.exports = function (grunt) { 'webpack-dev-server': { options: { webpack: { // webpack options - entry: "./src/tau.newCharts.js", + entry: "./src/tau.c" + + "harts.js", output: { // libraryTarget: "amd", library:'tauCharts', diff --git a/config.rb b/config.rb deleted file mode 100644 index 95d45372a..000000000 --- a/config.rb +++ /dev/null @@ -1,19 +0,0 @@ -# Require any additional compass plugins here. - -# Set this to the root of your project when deployed: -http_path = "/" -css_dir = "css" -sass_dir = "sass" -images_dir = "images" -javascripts_dir = "src" - -# You can select your preferred output style here (can be overridden via the command line): -# output_style = :expanded or :nested or :compact or :compressed - -# To enable relative paths to assets via compass helper functions. Uncomment: -# relative_assets = true - -# To disable debugging comments that display the original location of your selectors. Uncomment: -line_comments = false - -preferred_syntax = :sass diff --git a/src/tau.newCharts.js b/src/tau.charts.js similarity index 100% rename from src/tau.newCharts.js rename to src/tau.charts.js diff --git a/tasks/compile.js b/tasks/compile.js index 4dc862125..dcfc4eba4 100644 --- a/tasks/compile.js +++ b/tasks/compile.js @@ -49,7 +49,7 @@ module.exports = function (grunt) { var config = { include: ['../node_modules/almond/almond'], baseUrl: tmpDir + '/', - name: 'tau.newCharts', + name: 'tau.charts', exclude:['d3','underscore'], paths:{ 'underscore':'../libs/underscore', @@ -84,7 +84,7 @@ module.exports = function (grunt) { }, map: { '*': { - 'tauCharts': '../tau_modules/tau.newCharts', + 'tauCharts': '../tau_modules/tau.charts', 'canvg':'../bower_components/canvg/canvg', 'FileSaver':'../bower_components/FileSaver.js/FileSaver', 'rgbcolor': '../bower_components/canvg/rgbcolor', From fa30ff12c996c22a8c680a4bc92b57f0cae9222e Mon Sep 17 00:00:00 2001 From: konstantin Date: Tue, 24 Feb 2015 18:16:05 +0300 Subject: [PATCH 14/93] fix tests --- examples/scatter.html | 4 ++-- test/api-chart.test.js | 2 +- test/chart-bar.test.js | 2 +- test/chart-config.test.js | 2 +- test/chart-facet.test.js | 2 +- test/chart-horizontal-bar.test.js | 2 +- test/chart-line.test.js | 2 +- test/chart-scatterplot.test.js | 2 +- test/dsl-reader-layout.test.js | 2 +- test/dsl-reader.test.js | 2 +- test/element-interval.test.js | 2 +- test/element-line.test.js | 2 +- test/element-point.test.js | 2 +- test/formatter-registry.test.js | 2 +- test/plot.test.js | 2 +- test/spec-engine-factory.test.js | 2 +- test/tests-main.js | 2 +- test/unit-domain-period-generator.test.js | 2 +- test/utils/utils.js | 2 +- 19 files changed, 20 insertions(+), 20 deletions(-) diff --git a/examples/scatter.html b/examples/scatter.html index 56581c8a3..83deef62a 100644 --- a/examples/scatter.html +++ b/examples/scatter.html @@ -105,7 +105,7 @@ }, map: { '*': { - 'tauCharts': 'tau_modules/tau.newCharts', + 'tauCharts': 'tau_modules/tau.charts', 'print.style.css': 'node_modules/requirejs-text/text!plugins/print.style.css', 'rgbcolor': 'bower_components/canvg/rgbcolor', 'stackblur': 'bower_components/canvg/StackBlur', @@ -117,7 +117,7 @@ } }); - require(['tau_modules/tau.newCharts', 'src/addons/color-brewer', 'plugins/legend', 'plugins/trendline', 'plugins/export', 'plugins/tooltip', 'underscore'], function (tauCharts, brewer, legend, trendline, exportTo, tooltip) { + require(['tau_modules/tau.charts', 'src/addons/color-brewer', 'plugins/legend', 'plugins/trendline', 'plugins/export', 'plugins/tooltip', 'underscore'], function (tauCharts, brewer, legend, trendline, exportTo, tooltip) { /** @class Tooltip * @extends Plugin */ var defData = [ diff --git a/test/api-chart.test.js b/test/api-chart.test.js index ba5107133..c776afdab 100644 --- a/test/api-chart.test.js +++ b/test/api-chart.test.js @@ -6,7 +6,7 @@ define(function (require) { var schemes = require('schemes'); var utils = require('testUtils'); var $ = require('jquery'); - var tauCharts = require('tau_modules/tau.newCharts'); + var tauCharts = require('tau_modules/tau.charts'); var div, spec; var config = { diff --git a/test/chart-bar.test.js b/test/chart-bar.test.js index ccb4b5356..57c3ffd06 100644 --- a/test/chart-bar.test.js +++ b/test/chart-bar.test.js @@ -1,7 +1,7 @@ define(function(require){ var assert = require('chai').assert; var schemes = require('schemes'); - var tauChart = require('tau_modules/tau.newCharts'); + var tauChart = require('tau_modules/tau.charts'); describe('Bar chart', function () { var testData = [ {x: 1, y: 1, color: 'red', size: 6}, diff --git a/test/chart-config.test.js b/test/chart-config.test.js index 02a2b1511..ff8198690 100644 --- a/test/chart-config.test.js +++ b/test/chart-config.test.js @@ -1,7 +1,7 @@ define(function(require){ var assert = require('chai').assert; var expect = require('chai').expect; - var tauChart = require('tau_modules/tau.newCharts'); + var tauChart = require('tau_modules/tau.charts'); describe('Invalid chart definition', function(){ var testData = [ {x: 1, y: 1, color: 'red', size: 6}, diff --git a/test/chart-facet.test.js b/test/chart-facet.test.js index 1a8c58beb..4905298a0 100644 --- a/test/chart-facet.test.js +++ b/test/chart-facet.test.js @@ -2,7 +2,7 @@ define(function(require){ var assert = require('chai').assert; var expect = require('chai').expect; var schemes = require('schemes'); - var tauChart = require('tau_modules/tau.newCharts'); + var tauChart = require('tau_modules/tau.charts'); var facetSpec = schema({ diff --git a/test/chart-horizontal-bar.test.js b/test/chart-horizontal-bar.test.js index 3fa013788..6d8afb266 100644 --- a/test/chart-horizontal-bar.test.js +++ b/test/chart-horizontal-bar.test.js @@ -1,7 +1,7 @@ define(function (require) { var assert = require('chai').assert; var schemes = require('schemes'); - var tauChart = require('tau_modules/tau.newCharts'); + var tauChart = require('tau_modules/tau.charts'); describe('Horizontal bar chart', function () { var testData = [ {x: 1, y: 1, color: 'red', size: 6}, diff --git a/test/chart-line.test.js b/test/chart-line.test.js index 383c8c151..a05c54cc7 100644 --- a/test/chart-line.test.js +++ b/test/chart-line.test.js @@ -1,7 +1,7 @@ define(function (require) { var assert = require('chai').assert; var schemes = require('schemes'); - var tauChart = require('tau_modules/tau.newCharts'); + var tauChart = require('tau_modules/tau.charts'); describe('Line plot chart', function () { var testData = [ {x: 1, y: 1, color: 'red', size: 6}, diff --git a/test/chart-scatterplot.test.js b/test/chart-scatterplot.test.js index 8d30c1ffd..27c927cbe 100644 --- a/test/chart-scatterplot.test.js +++ b/test/chart-scatterplot.test.js @@ -1,7 +1,7 @@ define(function (require) { var assert = require('chai').assert; var schemes = require('schemes'); - var tauChart = require('tau_modules/tau.newCharts'); + var tauChart = require('tau_modules/tau.charts'); describe('scatter plot chart', function () { var testData = [ {x: 1, y: 1, color: 'red', size: 6}, diff --git a/test/dsl-reader-layout.test.js b/test/dsl-reader-layout.test.js index cb3237068..fee584ace 100644 --- a/test/dsl-reader-layout.test.js +++ b/test/dsl-reader-layout.test.js @@ -2,7 +2,7 @@ define(function (require) { var expect = require('chai').expect; var testUtils = require('testUtils'); var schemes = require('schemes'); - var tauChart = require('tau_modules/tau.newCharts'); + var tauChart = require('tau_modules/tau.charts'); var UnitDomainMixin = require('tau_modules/unit-domain-mixin').UnitDomainMixin; var UnitsRegistry = require('tau_modules/units-registry').UnitsRegistry; diff --git a/test/dsl-reader.test.js b/test/dsl-reader.test.js index d9b44e3d1..ba04e1bfc 100644 --- a/test/dsl-reader.test.js +++ b/test/dsl-reader.test.js @@ -2,7 +2,7 @@ define(function (require) { var expect = require('chai').expect; var testUtils = require('testUtils'); var schemes = require('schemes'); - var tauChart = require('tau_modules/tau.newCharts'); + var tauChart = require('tau_modules/tau.charts'); var UnitDomainMixin = require('tau_modules/unit-domain-mixin').UnitDomainMixin; var UnitsRegistry = require('tau_modules/units-registry').UnitsRegistry; diff --git a/test/element-interval.test.js b/test/element-interval.test.js index 40818c5ea..db4bd0b36 100644 --- a/test/element-interval.test.js +++ b/test/element-interval.test.js @@ -2,7 +2,7 @@ define(function (require) { var expect = require('chai').expect; var schemes = require('schemes'); var assert = require('chai').assert; - var tauChart = require('tau_modules/tau.newCharts').tauChart; + var tauChart = require('tau_modules/tau.charts').tauChart; var testUtils = require('testUtils'); var getGroupBar = testUtils.getGroupBar; var attrib = testUtils.attrib; diff --git a/test/element-line.test.js b/test/element-line.test.js index dc8b4c49a..4ef731cfa 100644 --- a/test/element-line.test.js +++ b/test/element-line.test.js @@ -6,7 +6,7 @@ define(function (require) { var assert = require('chai').assert; var getLine = testUtils.getLine; var attrib = testUtils.attrib; - var tauChart = require('tau_modules/tau.newCharts'); + var tauChart = require('tau_modules/tau.charts'); var cssClassMap = require('tau_modules/utils/css-class-map'); describe("ELEMENT.LINE", function () { diff --git a/test/element-point.test.js b/test/element-point.test.js index 950169b1d..d2e3fb8d4 100644 --- a/test/element-point.test.js +++ b/test/element-point.test.js @@ -3,7 +3,7 @@ define(function(require) { var schemes = require('schemes'); var tauBrewer = require('brewer'); var testUtils = require('testUtils'); - var tauCharts = require('tau_modules/tau.newCharts'); + var tauCharts = require('tau_modules/tau.charts'); var getDots = testUtils.getDots; var hasClass = testUtils.hasClass; var attrib = testUtils.attrib; diff --git a/test/formatter-registry.test.js b/test/formatter-registry.test.js index 914baee33..53485a7fc 100644 --- a/test/formatter-registry.test.js +++ b/test/formatter-registry.test.js @@ -1,7 +1,7 @@ define(function (require) { var expect = require('chai').expect; var schemes = require('schemes'); - var tauChart = require('tau_modules/tau.newCharts'); + var tauChart = require('tau_modules/tau.charts'); describe("Formatter registry", function () { var offsetHrs = new Date().getTimezoneOffset() / 60; diff --git a/test/plot.test.js b/test/plot.test.js index 242454a04..5353a3f93 100644 --- a/test/plot.test.js +++ b/test/plot.test.js @@ -2,7 +2,7 @@ define(function (require) { var expect = require('chai').expect; var schemes = require('schemes'); var modernizer = require('modernizer'); - var tauChart = require('tau_modules/tau.newCharts'); + var tauChart = require('tau_modules/tau.charts'); describe("tauChart.Plot", function () { var spec; diff --git a/test/spec-engine-factory.test.js b/test/spec-engine-factory.test.js index fce2f8c5f..c974811e3 100644 --- a/test/spec-engine-factory.test.js +++ b/test/spec-engine-factory.test.js @@ -1,7 +1,7 @@ define(function (require) { var testUtils = require('testUtils'); var expect = require('chai').expect; - var tauChart = require('tau_modules/tau.newCharts'); + var tauChart = require('tau_modules/tau.charts'); var UnitDomainMixin = require('tau_modules/unit-domain-mixin').UnitDomainMixin; var SpecEngineFactory = tauChart.__api__.SpecEngineFactory; diff --git a/test/tests-main.js b/test/tests-main.js index 0ba1de765..49828fb58 100644 --- a/test/tests-main.js +++ b/test/tests-main.js @@ -21,7 +21,7 @@ requirejs.config({ }, map: { '*': { - 'tauCharts': 'tau_modules/tau.newCharts', + 'tauCharts': 'tau_modules/tau.charts', 'print.style.css': 'node_modules/requirejs-text/text!plugins/print.style.css', 'rgbcolor': 'bower_components/canvg/rgbcolor', 'stackblur': 'bower_components/canvg/StackBlur', diff --git a/test/unit-domain-period-generator.test.js b/test/unit-domain-period-generator.test.js index a814c0054..fb29c6252 100644 --- a/test/unit-domain-period-generator.test.js +++ b/test/unit-domain-period-generator.test.js @@ -1,6 +1,6 @@ define(function (require) { var expect = require('chai').expect; - var tauChart = require('tau_modules/tau.newCharts'); + var tauChart = require('tau_modules/tau.charts'); describe("Unit domain period generator", function () { var offsetHrs = new Date().getTimezoneOffset() / 60; diff --git a/test/utils/utils.js b/test/utils/utils.js index 4b05d57a8..c1a5d4afb 100644 --- a/test/utils/utils.js +++ b/test/utils/utils.js @@ -1,5 +1,5 @@ define(function (require) { - var tauCharts = require('tau_modules/tau.newCharts'), + var tauCharts = require('tau_modules/tau.charts'), $ = require('jquery'), d3 = require('d3'); From f6ab41e3153f7d41eb1b759a159215d9f9bbd7d3 Mon Sep 17 00:00:00 2001 From: konstantin Date: Tue, 24 Feb 2015 18:42:28 +0300 Subject: [PATCH 15/93] update grunt config --- Gruntfile.js | 46 +++++++++++++++++++++++++++-------------- examples/streaming.html | 2 +- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 4e0e2377d..76690b1de 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -8,7 +8,32 @@ module.exports = function (grunt) { "*.js", "**/*.js", '!addons/*.js' - ]; + ], + webpackConf = { // webpack options + entry: "./src/tau.charts.js", + output: { + // libraryTarget: "amd", + library:'tauCharts', + path: "build/development", + filename: "tauCharts.js" + }, + externals: { + // require("jquery") is external and available + // on the global var jQuery + "d3": "d3", + "_":'underscore' + }, + module: { + loaders: [ + {test: /\.js$/, exclude: /node_modules/, loader: "babel-loader"} + ] + }/*, + plugins:[ + new webpack.optimize.UglifyJsPlugin(), + new webpack.optimize.OccurenceOrderPlugin(), + new webpack.optimize.DedupePlugin() + ]*/ + }; grunt.initConfig({ // Metadata. pkg: grunt.file.readJSON('package.json'), @@ -257,23 +282,12 @@ module.exports = function (grunt) { tasks: ['less'] } }, + webpack:{ + build: webpackConf + }, 'webpack-dev-server': { options: { - webpack: { // webpack options - entry: "./src/tau.c" + - "harts.js", - output: { - // libraryTarget: "amd", - library:'tauCharts', - path: "build/", - filename: "build.js" - }, - module: { - loaders: [ - {test: /\.js$/, exclude: /node_modules/, loader: "babel-loader"} - ] - } - }, + webpack:webpackConf, publicPath: "/" }, start: { diff --git a/examples/streaming.html b/examples/streaming.html index 0d2721ded..6ed970807 100644 --- a/examples/streaming.html +++ b/examples/streaming.html @@ -95,7 +95,7 @@
- + - - - - - - + + diff --git a/package.json b/package.json index 36f643ec5..63a455f55 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,8 @@ } ], "dependencies": { - "d3": "~3.4.3", - "underscore": "~1.6.0" + "d3": "~3.5.5", + "underscore": "~1.8.2" }, "repository": { "type": "git", From 921bd59e599a36056668e08244b42ea2b8019766 Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Tue, 24 Feb 2015 20:47:14 +0300 Subject: [PATCH 17/93] Draft d3 update pattern for cartesian coordinates --- examples/gpl.html | 3 +- examples/streaming.html | 32 +------ src/charts/tau.gpl.js | 15 +-- src/elements/coords.cartesian.js | 154 ++++++++++++++++++------------- 4 files changed, 106 insertions(+), 98 deletions(-) diff --git a/examples/gpl.html b/examples/gpl.html index 9593155c2..3f8848ac9 100644 --- a/examples/gpl.html +++ b/examples/gpl.html @@ -22,7 +22,8 @@ - + + + diff --git a/src/charts/tau.gpl.js b/src/charts/tau.gpl.js index b28d7200b..5adf0adaa 100644 --- a/src/charts/tau.gpl.js +++ b/src/charts/tau.gpl.js @@ -91,7 +91,7 @@ export class GPL extends Emitter { this.trans = config.trans; } - render(target, xSize) { + renderTo(target, xSize) { var targetNode = d3.select(target).node(); if (targetNode === null) { diff --git a/src/elements/element.interval.js b/src/elements/element.interval.js new file mode 100644 index 000000000..aeef888ca --- /dev/null +++ b/src/elements/element.interval.js @@ -0,0 +1,37 @@ +import {CSS_PREFIX} from '../const'; +import {interval} from './interval'; + +export class Interval { + + constructor(config) { + super(); + + this.config = config; + } + + drawLayout(fnCreateScale) { + + var config = this.config; + + this.xScale = fnCreateScale('pos', config.x, [0, config.options.width]); + this.yScale = fnCreateScale('pos', config.y, [config.options.height, 0]); + this.color = fnCreateScale('color', config.color, {}); + this.size = fnCreateScale('size', config.size, {}); + + return this; + } + + drawFrames(frames) { + var canvas = this.config.options.container; + + var xScale = this.xScale; + var yScale = this.yScale; + var cScale = this.color; + var sScale = this.size; + + return frames.map((frame) => { + frame.take(); + return frame; + }); + } +} \ No newline at end of file diff --git a/src/tau.charts.js b/src/tau.charts.js index 22ed386e9..1323b8eca 100644 --- a/src/tau.charts.js +++ b/src/tau.charts.js @@ -15,6 +15,7 @@ import {Cartesian} from './elements/coords.cartesian'; import {Point} from './elements/element.point'; import {Line} from './elements/element.line'; import {Pie} from './elements/element.pie'; +import {Interval} from './elements/element.interval'; var colorBrewers = {}; var plugins = {}; @@ -125,6 +126,7 @@ api.UnitsRegistry .reg('RECT', Cartesian) .reg('POINT', Point) + .reg('INTERVAL', Interval) .reg('LINE', Line) .reg('PIE', Pie); From 76c1be4d633a76c563336c0e852b42781ba47249 Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Wed, 25 Feb 2015 18:21:24 +0300 Subject: [PATCH 26/93] Extract hash function for data frame. Improve demo page --- examples/streaming-demo.html | 308 +++++++++++++++++++++++++++++++ src/charts/tau.gpl.js | 2 + src/elements/coords.cartesian.js | 134 +++++++------- src/elements/element.point.js | 50 +++-- 4 files changed, 416 insertions(+), 78 deletions(-) create mode 100644 examples/streaming-demo.html diff --git a/examples/streaming-demo.html b/examples/streaming-demo.html new file mode 100644 index 000000000..4ba7a878d --- /dev/null +++ b/examples/streaming-demo.html @@ -0,0 +1,308 @@ + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ +
+ + + + + + + \ No newline at end of file diff --git a/src/charts/tau.gpl.js b/src/charts/tau.gpl.js index 5adf0adaa..e4b942a65 100644 --- a/src/charts/tau.gpl.js +++ b/src/charts/tau.gpl.js @@ -125,6 +125,7 @@ export class GPL extends Emitter { this.root.options = { container: container.select('.frame-root'), + frameId: 'root', left: 0, top: 0, width: size.width, @@ -207,6 +208,7 @@ export class GPL extends Emitter { _datify(frame) { var data = this.sources[frame.source].data; var trans = this.trans; + frame.hash = () => btoa([frame.pipe, frame.key, frame.source].map(JSON.stringify).join('')).replace(/=/g, '_'); frame.take = () => frame.pipe.reduce((data, pipeCfg) => trans[pipeCfg.type](data, pipeCfg.args), data); frame.data = frame.take(); return frame; diff --git a/src/elements/coords.cartesian.js b/src/elements/coords.cartesian.js index b99667df6..c71a90657 100644 --- a/src/elements/coords.cartesian.js +++ b/src/elements/coords.cartesian.js @@ -333,9 +333,9 @@ export class Cartesian { var unit = this.config; if (unit.guide.autoLayout === 'extract-axes') { - var containerBox = unit.options.container.node().getBBox(); + var containerHeight = unit.options.containerHeight; var guide = unit.guide = unit.guide || {}; - guide.x.hide = ((unit.options.top + unit.options.height) < containerBox.height); + guide.x.hide = ((unit.options.top + unit.options.height) < containerHeight); guide.y.hide = ((unit.options.left > 0)); } } @@ -437,12 +437,14 @@ export class Cartesian { var xPart = self.W / xDomain.length; var yPart = self.H / yDomain.length; - var frameId = fnBase64(frame); + var frameId = frame.hash(); mapper = (unit) => { unit.options = { frameId: frameId, container: cell, + containerWidth: self.W, + containerHeight: self.H, left: coordX - xPart / 2, top: coordY - yPart / 2, width: xPart, @@ -454,6 +456,8 @@ export class Cartesian { mapper = (unit) => { unit.options = { container: cell, + containerWidth: self.W, + containerHeight: self.H, left: 0, top: 0, width: self.W, @@ -471,27 +475,19 @@ export class Cartesian { }); }; - var fnBase64 = (frame) => btoa([ - JSON.stringify(frame.pipe), - JSON.stringify(frame.key), - JSON.stringify(frame.source) - ].join('') - ).replace(/=/g, '_'); - var cells = this .grid .selectAll(`.parent-frame-${options.frameId}`) - .data(frames, fnBase64); - - cells - .call(updateHandler); + .data(frames, (f) => f.hash()); cells .exit() .remove(); + cells + .call(updateHandler); cells .enter() .append('g') - .attr('class', (d) => (`${CSS_PREFIX}cell cell parent-frame-${options.frameId} frame-${fnBase64(d)}`)) + .attr('class', (d) => (`${CSS_PREFIX}cell cell parent-frame-${options.frameId} frame-${d.hash()}`)) .call(updateHandler); } @@ -543,69 +539,75 @@ export class Cartesian { grid.enter() .append('g') .attr('class', 'grid grid_' + frameId) - .attr('transform', translate(0, 0)); + .attr('transform', translate(0, 0)) + .call((selection) => { - grid = container.select('.grid_' + frameId); + if (selection.empty()) { + return; + } - var linesOptions = (node.guide.showGridLines || '').toLowerCase(); - if (linesOptions.length > 0) { + var grid = selection; - var gridLines = grid.append('g').attr('class', 'grid-lines'); + var linesOptions = (node.guide.showGridLines || '').toLowerCase(); + if (linesOptions.length > 0) { - if ((linesOptions.indexOf('x') > -1) && node.x.scaleDim) { - var x = node.x; - var xGridAxis = d3.svg - .axis() - .scale(x.scaleObj) - .orient(x.guide.scaleOrient) - .tickSize(H); + var gridLines = grid.append('g').attr('class', 'grid-lines'); - let formatter = FormatterRegistry.get(x.guide.tickFormat); - if (formatter !== null) { - xGridAxis.ticks(Math.round(W / x.guide.density)); - xGridAxis.tickFormat(formatter); - } + if ((linesOptions.indexOf('x') > -1) && node.x.scaleDim) { + var x = node.x; + var xGridAxis = d3.svg + .axis() + .scale(x.scaleObj) + .orient(x.guide.scaleOrient) + .tickSize(H); - var xGridLines = gridLines.append('g').attr('class', 'grid-lines-x').call(xGridAxis); + let formatter = FormatterRegistry.get(x.guide.tickFormat); + if (formatter !== null) { + xGridAxis.ticks(Math.round(W / x.guide.density)); + xGridAxis.tickFormat(formatter); + } - decorateAxisTicks(xGridLines, x, W); + var xGridLines = gridLines.append('g').attr('class', 'grid-lines-x').call(xGridAxis); - var firstXGridLine = xGridLines.select('g.tick'); - if (firstXGridLine.node() && firstXGridLine.attr('transform') !== 'translate(0,0)') { - var zeroNode = firstXGridLine.node().cloneNode(true); - gridLines.node().appendChild(zeroNode); - d3.select(zeroNode) - .attr('class', 'border') - .attr('transform', translate(0, 0)) - .select('line') - .attr('x1', 0) - .attr('x2', 0); - } - } + decorateAxisTicks(xGridLines, x, W); - if ((linesOptions.indexOf('y') > -1) && node.y.scaleDim) { - var y = node.y; - var yGridAxis = d3.svg - .axis() - .scale(y.scaleObj) - .orient(y.guide.scaleOrient) - .tickSize(-W); - - let formatter = FormatterRegistry.get(y.guide.tickFormat); - if (formatter !== null) { - yGridAxis.ticks(Math.round(H / y.guide.density)); - yGridAxis.tickFormat(formatter); - } + var firstXGridLine = xGridLines.select('g.tick'); + if (firstXGridLine.node() && firstXGridLine.attr('transform') !== 'translate(0,0)') { + var zeroNode = firstXGridLine.node().cloneNode(true); + gridLines.node().appendChild(zeroNode); + d3.select(zeroNode) + .attr('class', 'border') + .attr('transform', translate(0, 0)) + .select('line') + .attr('x1', 0) + .attr('x2', 0); + } + } - var yGridLines = gridLines.append('g').attr('class', 'grid-lines-y').call(yGridAxis); + if ((linesOptions.indexOf('y') > -1) && node.y.scaleDim) { + var y = node.y; + var yGridAxis = d3.svg + .axis() + .scale(y.scaleObj) + .orient(y.guide.scaleOrient) + .tickSize(-W); + + let formatter = FormatterRegistry.get(y.guide.tickFormat); + if (formatter !== null) { + yGridAxis.ticks(Math.round(H / y.guide.density)); + yGridAxis.tickFormat(formatter); + } - decorateAxisTicks(yGridLines, y, H); - fixAxisBottomLine(yGridLines, y, H); - } + var yGridLines = gridLines.append('g').attr('class', 'grid-lines-y').call(yGridAxis); - // TODO: make own axes and grid instead of using d3's in such tricky way - gridLines.selectAll('text').remove(); - } + decorateAxisTicks(yGridLines, y, H); + fixAxisBottomLine(yGridLines, y, H); + } + + // TODO: make own axes and grid instead of using d3's in such tricky way + gridLines.selectAll('text').remove(); + } + }); return grid; } diff --git a/src/elements/element.point.js b/src/elements/element.point.js index 7c89e317c..612501480 100644 --- a/src/elements/element.point.js +++ b/src/elements/element.point.js @@ -22,6 +22,8 @@ export class Point { drawFrames(frames) { + var prefix = `${CSS_PREFIX}dot dot i-role-element i-role-datum`; + var canvas = this.config.options.container; var xScale = this.xScale; @@ -29,24 +31,48 @@ export class Point { var cScale = this.color; var sScale = this.size; - var update = function () { - - var props = { - r: 0, - cx: (d) => xScale(d[xScale.dim]), - cy: (d) => yScale(d[yScale.dim]), - class: (d) => `${CSS_PREFIX}dot dot i-role-element i-role-datum ${cScale(d[cScale.dim])}` + var enter = (frameId) => { + return function () { + return this + .attr({ + r: 0, + cx: (d) => xScale(d[xScale.dim]), + cy: (d) => yScale(d[yScale.dim]), + class: (d) => `${prefix} ${cScale(d[cScale.dim])} frame-${frameId}` + }) + .transition() + .duration(500) + .attr('r', (d) => sScale(d[sScale.dim])); }; + }; - return this.attr(props).transition().duration(500).attr('r', (d) => sScale(d[sScale.dim])); + var update = (frameId) => { + return function () { + return this + .attr({ + r: (d) => sScale(d[sScale.dim]), + cx: (d) => xScale(d[xScale.dim]), + cy: (d) => yScale(d[yScale.dim]), + class: (d) => `${prefix} ${cScale(d[cScale.dim])} frame-${frameId}` + }); + }; }; frames.map((frame) => { + var frameKey = frame.hash(); var elements; - elements = canvas.selectAll('.dot').data(frame.take()); -// elements.call(update); - elements.exit().remove(); - elements.enter().append('circle').call(update); + elements = canvas + .selectAll(`.frame-${frameKey}`) + .data(frame.take()); + elements + .exit() + .remove(); + elements + .call(update(frameKey)); + elements + .enter() + .append('circle') + .call(enter(frameKey)); }); return []; From b33437b5deabe7043edcf82a0c3df743ea9cee56 Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Wed, 25 Feb 2015 18:40:05 +0300 Subject: [PATCH 27/93] Demo pages are fixed --- examples/gpl.html | 6 +++--- examples/streaming-demo.html | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/gpl.html b/examples/gpl.html index 259b97ef6..ebc818cf3 100644 --- a/examples/gpl.html +++ b/examples/gpl.html @@ -451,7 +451,7 @@ var gpl = new tauCharts.GPL(facetSpec); - gpl.render('#container'); + gpl.renderTo('#container'); @@ -993,7 +993,7 @@ var gpl = new tauCharts.GPL(dashSpec); - gpl.render('#dashboard'); + gpl.renderTo('#dashboard'); diff --git a/examples/streaming-demo.html b/examples/streaming-demo.html index 4ba7a878d..51f928aa1 100644 --- a/examples/streaming-demo.html +++ b/examples/streaming-demo.html @@ -183,7 +183,7 @@ x: {padding: 40, label: {text: 'project'}, cssClass:'x axis facet-axis'}, y: {padding: 40, label: {text: 'team'}, cssClass:'y axis facet-axis'} }, - unit: [ + units: [ { type: 'RECT', x: 'count', @@ -198,7 +198,7 @@ x: {padding: 10, label: {text: 'count'}}, y: {padding: 10, label: {text: 'story'}} }, - unit: [ + units: [ { type: 'POINT', x: 'count', @@ -217,7 +217,7 @@ var size = {width: 800, height: 600}; - chart.render('#line', size); + chart.renderTo('#line', size); var stop = false; var c = 0; @@ -249,7 +249,7 @@ date: new Date('2014-10-05') }); - chart.render('#line', size); + chart.renderTo('#line', size); },1000); @@ -277,7 +277,7 @@ date: new Date('2014-10-05') }); - chart.render('#line', size); + chart.renderTo('#line', size); }; var f = 1; @@ -298,7 +298,7 @@ date: new Date('2014-10-05') }); - chart.render('#line', size); + chart.renderTo('#line', size); }; From b45b9434913ea1052853652d5aa30cbdbc730ced Mon Sep 17 00:00:00 2001 From: konstantin Date: Thu, 26 Feb 2015 16:31:02 +0300 Subject: [PATCH 28/93] new interval element --- examples/gpl.html | 14 ++++++++------ examples/scatter.html | 11 +++++++++-- examples/streaming.html | 8 ++++---- src/elements/element.interval.js | 31 ++++++++++++++++++++++++++++--- src/elements/interval.js | 4 +++- 5 files changed, 52 insertions(+), 16 deletions(-) diff --git a/examples/gpl.html b/examples/gpl.html index ebc818cf3..9d21e6c85 100644 --- a/examples/gpl.html +++ b/examples/gpl.html @@ -372,7 +372,8 @@ }, data: [ {project: 'TP3', team: 'alpha', story: 'A1', bug: 'ISSUE1', count: 10, date: new Date('2014-01-05')}, - {project: 'tau', team: 'xbeta', story: 'B1', bug: 'ISSUE2', count: 15, date: new Date('2014-10-05')} + {project: 'tau', team: 'xbeta', story: 'B1', bug: 'ISSUE2', count: 15, date: new Date('2014-10-05')}, + {project: 'tau', team: 'xbeta', story: 'A1', bug: 'ISSUE2', count: 15, date: new Date('2014-10-05')}, ] } }, @@ -421,8 +422,8 @@ units: [ { type: 'RECT', - x: 'count', - y: 'story', + x: 'story', + y: 'count', expression: { source: '/', inherit: true @@ -435,9 +436,10 @@ }, units: [ { - type: 'POINT', - x: 'count', - y: 'story', + type: 'INTERVAL', + x: 'story', + y: 'count', + color:'team', expression: { source: '/', inherit: true diff --git a/examples/scatter.html b/examples/scatter.html index 83deef62a..e424ba6df 100644 --- a/examples/scatter.html +++ b/examples/scatter.html @@ -117,7 +117,14 @@ } }); - require(['tau_modules/tau.charts', 'src/addons/color-brewer', 'plugins/legend', 'plugins/trendline', 'plugins/export', 'plugins/tooltip', 'underscore'], function (tauCharts, brewer, legend, trendline, exportTo, tooltip) { + require([ + 'tau_modules/tau.charts', + 'src/addons/color-brewer', + 'plugins/legend', + 'plugins/trendline', + 'plugins/export', + 'plugins/tooltip', + 'underscore'], function (tauCharts, brewer, legend, trendline, exportTo, tooltip) { /** @class Tooltip * @extends Plugin */ var defData = [ @@ -159,7 +166,7 @@ },*/ plugins: [legend(), trendline(), exportTo({cssPaths:['../css/tauCharts.css']}), tooltip()], data: defData, - type: 'scatterplot', + type: 'bar', x: 'date', y: 'count', size:'count', diff --git a/examples/streaming.html b/examples/streaming.html index b46f7a036..431384506 100644 --- a/examples/streaming.html +++ b/examples/streaming.html @@ -161,8 +161,8 @@ unit:{ type: 'RECT', - x: 'count', - y: 'story', + x: 'story', + y: 'count', expression: { inherit: false, source: '/', @@ -178,8 +178,8 @@ units: [ { type: 'INTERVAL', - x: 'count', - y: 'story', + x: 'story', + y: 'count', expression: { source: '/', inherit: true diff --git a/src/elements/element.interval.js b/src/elements/element.interval.js index aeef888ca..9f799fcaa 100644 --- a/src/elements/element.interval.js +++ b/src/elements/element.interval.js @@ -23,14 +23,39 @@ export class Interval { drawFrames(frames) { var canvas = this.config.options.container; - + var config = this.config; var xScale = this.xScale; var yScale = this.yScale; - var cScale = this.color; + var color = this.color; var sScale = this.size; return frames.map((frame) => { - frame.take(); + // frame.take(); + var node = { + options: { + container: canvas, + xScale, + yScale, + color, + width:config.options.width, + height:config.options.height + }, + x: xScale, + y: yScale, + color: color, + groupBy() { + return d3.nest() + .key(function (d) { + return d[color.scaleDim]; + }) + .entries(frame.take()); + }, + partition() { + }, + source() { + } + }; + interval(node); return frame; }); } diff --git a/src/elements/interval.js b/src/elements/interval.js index 48dec9342..c0aa1a7ad 100644 --- a/src/elements/interval.js +++ b/src/elements/interval.js @@ -151,7 +151,9 @@ var interval = function (node) { .attr('height', calculateHeight) .attr('width', calculateWidth) // jscs:disable - .attr('class', (d) => (`i-role-element i-role-datum bar ${CSS_PREFIX}bar ${colorScale(d[node.color.scaleDim])}`)) + .attr('class', (d) =>{ + return (`i-role-element i-role-datum bar ${CSS_PREFIX}bar ${colorScale(d[node.color.scaleDim])}`) + }) // jscs:enable .attr('x', calculateX) .attr('y', calculateY); From 27f9709858ef3b92eb4ec5270fdd6edfc2becbb1 Mon Sep 17 00:00:00 2001 From: konstantin Date: Thu, 26 Feb 2015 16:42:42 +0300 Subject: [PATCH 29/93] fix jsHint error --- src/elements/interval.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/elements/interval.js b/src/elements/interval.js index c0aa1a7ad..c61366680 100644 --- a/src/elements/interval.js +++ b/src/elements/interval.js @@ -152,7 +152,7 @@ var interval = function (node) { .attr('width', calculateWidth) // jscs:disable .attr('class', (d) =>{ - return (`i-role-element i-role-datum bar ${CSS_PREFIX}bar ${colorScale(d[node.color.scaleDim])}`) + return (`i-role-element i-role-datum bar ${CSS_PREFIX}bar ${colorScale(d[node.color.scaleDim])}`); }) // jscs:enable .attr('x', calculateX) From 7cba7e0812868129a3ba927ff83e53c70693f186 Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Thu, 26 Feb 2015 20:03:33 +0300 Subject: [PATCH 30/93] Migrate line and point elements to d3 update pattern --- examples/gpl.html | 28 +++++-- examples/streaming-demo.html | 1 + src/charts/tau.gpl.js | 4 +- src/elements/coords.cartesian.js | 11 +-- src/elements/element.line.js | 139 ++++++++++++++++++++----------- src/elements/element.point.js | 95 ++++++++++++--------- 6 files changed, 174 insertions(+), 104 deletions(-) diff --git a/examples/gpl.html b/examples/gpl.html index 9d21e6c85..3631f7c21 100644 --- a/examples/gpl.html +++ b/examples/gpl.html @@ -451,9 +451,9 @@ } }; - var gpl = new tauCharts.GPL(facetSpec); + var facetGpl = new tauCharts.GPL(facetSpec); - gpl.renderTo('#container'); + facetGpl.renderTo('#container'); @@ -993,9 +1011,9 @@ } }; - var gpl = new tauCharts.GPL(dashSpec); + var dashGpl = new tauCharts.GPL(dashSpec); - gpl.renderTo('#dashboard'); + dashGpl.renderTo('#dashboard'); diff --git a/examples/streaming-demo.html b/examples/streaming-demo.html index 51f928aa1..119f92442 100644 --- a/examples/streaming-demo.html +++ b/examples/streaming-demo.html @@ -226,6 +226,7 @@ if (stop === true) { clearInterval(interval); + return; } diff --git a/src/charts/tau.gpl.js b/src/charts/tau.gpl.js index e4b942a65..df61a9cba 100644 --- a/src/charts/tau.gpl.js +++ b/src/charts/tau.gpl.js @@ -117,9 +117,7 @@ export class GPL extends Emitter { .data(['const']) .enter() .append('svg') -// jscs:disable disallowSpacesInsideObjectBrackets - .attr(_.extend({class: `${CSS_PREFIX}svg`}, size)) -// jscs:enable disallowSpacesInsideObjectBrackets + .attr(_.extend({class: (`${CSS_PREFIX}svg`)}, size)) .append('g') .attr('class', `${CSS_PREFIX}cell cell frame-root`); diff --git a/src/elements/coords.cartesian.js b/src/elements/coords.cartesian.js index c71a90657..e4f115608 100644 --- a/src/elements/coords.cartesian.js +++ b/src/elements/coords.cartesian.js @@ -426,6 +426,7 @@ export class Cartesian { frames.reduce( (units, frame) => { var mapper; + var frameId = frame.hash(); if (frame.key) { var coordX = self.x(frame.key[self.x.dim]); @@ -437,10 +438,9 @@ export class Cartesian { var xPart = self.W / xDomain.length; var yPart = self.H / yDomain.length; - var frameId = frame.hash(); - - mapper = (unit) => { + mapper = (unit, i) => { unit.options = { + uid: frameId + i, frameId: frameId, container: cell, containerWidth: self.W, @@ -453,8 +453,9 @@ export class Cartesian { return unit; }; } else { - mapper = (unit) => { + mapper = (unit, i) => { unit.options = { + uid: frameId + i, container: cell, containerWidth: self.W, containerHeight: self.H, @@ -467,7 +468,7 @@ export class Cartesian { }; } - frame.units.map((u) => continuation(mapper(u), frame)); + frame.units.map(mapper).map((unit) => continuation(unit, frame)); return units.concat(frame.units.map(mapper)); }, diff --git a/src/elements/element.line.js b/src/elements/element.line.js index f6cf98aa1..3b95fdf3b 100644 --- a/src/elements/element.line.js +++ b/src/elements/element.line.js @@ -9,7 +9,8 @@ export class Line { this.config.guide = _.defaults( this.config.guide, { - cssClass: '' + cssClass: '', + anchors: false } ); } @@ -28,71 +29,109 @@ export class Line { drawFrames(frames) { - var config = this.config; - + var guide = this.config.guide; var options = this.config.options; var xScale = this.xScale; var yScale = this.yScale; var colorScale = this.color; + var sizeScale = this.size; - var widthClass = getLineClassesByWidth(options.width); - var countClass = getLineClassesByCount(frames.length); + var widthCss = getLineClassesByWidth(options.width); + var countCss = getLineClassesByCount(frames.length); + var d3Line = d3.svg + .line() + .x((d) => xScale(d[xScale.dim])) + .y((d) => yScale(d[yScale.dim])); + + if (guide.interpolate) { + d3Line.interpolate(guide.interpolate); + } + + var linePref = `${CSS_PREFIX}line i-role-element i-role-datum line ${widthCss} ${countCss} ${guide.cssClass}`; var updateLines = function () { - // jscs:disable - this.attr('class', (d) => `${CSS_PREFIX}line i-role-element i-role-datum line ${colorScale(d.key)} ${widthClass} ${countClass} ${config.guide.cssClass}`); - // jscs:enable - var paths = this.selectAll('path').data((d) => [d.take()]); - paths.call(updatePaths); - paths.enter().append('path').call(updatePaths); - paths.exit().remove(); + var paths = this + .selectAll('path') + .data((frame) => [frame.data]); + paths + .exit() + .remove(); + paths + .attr('d', d3Line); + paths + .enter() + .append('path') + .attr('d', d3Line); }; - var drawPoints = function (points) { - var update = function () { - return this - .attr('r', 1.5) - // jscs:disable - .attr('class', (d) => `${CSS_PREFIX}dot-line dot-line i-role-element ${CSS_PREFIX}dot i-role-datum ${colorScale(d[colorScale.dim])}`) - // jscs:enable - .attr('cx', (d) => xScale(d[xScale.dim])) - .attr('cy', (d) => yScale(d[yScale.dim])); - }; - - var elements = options.container.selectAll('.dot-line').data(points); - elements.call(update); - elements.exit().remove(); - elements.enter().append('circle').call(update); + var pointPref = `${CSS_PREFIX}dot-line dot-line i-role-element ${CSS_PREFIX}dot i-role-datum`; + var updatePoints = function () { + + var points = this + .selectAll('circle') + .data((frame) => frame.data); + points + .exit() + .remove(); + points + .attr({ + r: (d) => sizeScale(d[sizeScale.dim]), + cx: (d) => xScale(d[xScale.dim]), + cy: (d) => yScale(d[yScale.dim]), + class: (d) => (`${pointPref} ${colorScale(d[colorScale.dim])}`) + }); + points + .enter() + .append('circle') + .attr({ + r: (d) => sizeScale(d[sizeScale.dim]), + cx: (d) => xScale(d[xScale.dim]), + cy: (d) => yScale(d[yScale.dim]), + class: (d) => (`${pointPref} ${colorScale(d[colorScale.dim])}`) + }); }; - var line = d3.svg.line().x((d) => xScale(d[xScale.dim])).y((d) => yScale(d[yScale.dim])); + var updateGroups = (x, drawPath, drawPoints) => { - if (this.config.guide.interpolate) { - line.interpolate(this.config.guide.interpolate); - } + return function () { + + this.attr('class', (f) => `${linePref} ${colorScale(f.tags[colorScale.dim])} ${x} frame-${f.hash}`) + .call(function () { + + if (drawPath) { + updateLines.call(this); + } - var updatePaths = function () { - this.attr('d', line); + if (drawPoints) { + updatePoints.call(this); + } + }); + }; }; - var points = frames.reduce( - function (points, item) { - var values = item.take(); - if (values.length === 1) { - points.push(values[0]); - } - return points; - }, - []); - - if (points.length > 0) { - drawPoints(points); - } + var mapper = (f) => ({tags: f.key || {}, hash: f.hash(), data: f.take()}); + + var drawFrame = (tag, id, filter) => { + + var isDrawLine = tag === 'line'; + var isDrawAnch = !isDrawLine || guide.anchors; + + var frameGroups = options.container + .selectAll(`.frame-${id}`) + .data(frames.map(mapper).filter(filter), (f) => f.hash); + frameGroups + .exit() + .remove(); + frameGroups + .call(updateGroups((`frame-${id}`), isDrawLine, isDrawAnch)); + frameGroups + .enter() + .append('g') + .call(updateGroups((`frame-${id}`), isDrawLine, isDrawAnch)); + }; - var lines = options.container.selectAll('.line' + parseInt(Math.random() * 1000)).data(frames); - lines.call(updateLines); - lines.enter().append('g').call(updateLines); - lines.exit().remove(); + drawFrame('line', 'line-' + options.uid, (f) => f.data.length > 1); + drawFrame('anch', 'anch-' + options.uid, (f) => f.data.length < 2); } } \ No newline at end of file diff --git a/src/elements/element.point.js b/src/elements/element.point.js index 612501480..34d336276 100644 --- a/src/elements/element.point.js +++ b/src/elements/element.point.js @@ -22,58 +22,71 @@ export class Point { drawFrames(frames) { - var prefix = `${CSS_PREFIX}dot dot i-role-element i-role-datum`; + var options = this.config.options; - var canvas = this.config.options.container; + var prefix = `${CSS_PREFIX}dot dot i-role-element i-role-datum`; var xScale = this.xScale; var yScale = this.yScale; var cScale = this.color; var sScale = this.size; - var enter = (frameId) => { - return function () { - return this - .attr({ - r: 0, - cx: (d) => xScale(d[xScale.dim]), - cy: (d) => yScale(d[yScale.dim]), - class: (d) => `${prefix} ${cScale(d[cScale.dim])} frame-${frameId}` - }) - .transition() - .duration(500) - .attr('r', (d) => sScale(d[sScale.dim])); - }; + var enter = function () { + return this + .attr({ + r: 0, + cx: (d) => xScale(d[xScale.dim]), + cy: (d) => yScale(d[yScale.dim]), + class: (d) => `${prefix} ${cScale(d[cScale.dim])}` + }) + .transition() + .duration(500) + .attr('r', (d) => sScale(d[sScale.dim])); + }; + + var update = function () { + return this + .attr({ + r: (d) => sScale(d[sScale.dim]), + cx: (d) => xScale(d[xScale.dim]), + cy: (d) => yScale(d[yScale.dim]), + class: (d) => `${prefix} ${cScale(d[cScale.dim])}` + }); }; - var update = (frameId) => { - return function () { - return this - .attr({ - r: (d) => sScale(d[sScale.dim]), - cx: (d) => xScale(d[xScale.dim]), - cy: (d) => yScale(d[yScale.dim]), - class: (d) => `${prefix} ${cScale(d[cScale.dim])} frame-${frameId}` - }); - }; + var updateGroups = function () { + + this.attr('class', (f) => `frame-id-${options.uid} frame-${f.hash}`) + .call(function () { + var points = this + .selectAll('circle') + .data((frame) => frame.data); + points + .exit() + .remove(); + points + .call(update); + points + .enter() + .append('circle') + .call(enter); + }); }; - frames.map((frame) => { - var frameKey = frame.hash(); - var elements; - elements = canvas - .selectAll(`.frame-${frameKey}`) - .data(frame.take()); - elements - .exit() - .remove(); - elements - .call(update(frameKey)); - elements - .enter() - .append('circle') - .call(enter(frameKey)); - }); + var mapper = (f) => ({tags: f.key || {}, hash: f.hash(), data: f.take()}); + + var frameGroups = options.container + .selectAll('.frame-id-' + options.uid) + .data(frames.map(mapper), (f) => f.hash); + frameGroups + .exit() + .remove(); + frameGroups + .call(updateGroups); + frameGroups + .enter() + .append('g') + .call(updateGroups); return []; } From 1a21ffdd3d60347bce381ebc96028471dffe0cfb Mon Sep 17 00:00:00 2001 From: konstantin Date: Mon, 2 Mar 2015 15:22:20 +0300 Subject: [PATCH 31/93] add groupBy method --- src/charts/tau.gpl.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/charts/tau.gpl.js b/src/charts/tau.gpl.js index df61a9cba..bf0c34d5d 100644 --- a/src/charts/tau.gpl.js +++ b/src/charts/tau.gpl.js @@ -40,7 +40,11 @@ var FramesAlgebra = { }, []); }, - + groupBy(dataFn, dim) { + var data = dataFn(); + var domainX = _(data).chain().pluck(dim).unique().value(); + return domainX.map((x)=>({[dim]:x})); + }, none: function (datus, dimX, dimY, pipe) { return [null]; } From b2a1f6089c7de1b3986ffe3a02b25ccccb49fefa Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Mon, 2 Mar 2015 18:47:19 +0300 Subject: [PATCH 32/93] Generate cell layers using d3 update pattern --- src/elements/coords.cartesian.js | 126 +++++++++++++++++-------------- 1 file changed, 68 insertions(+), 58 deletions(-) diff --git a/src/elements/coords.cartesian.js b/src/elements/coords.cartesian.js index e4f115608..4fc0a3ad1 100644 --- a/src/elements/coords.cartesian.js +++ b/src/elements/coords.cartesian.js @@ -416,64 +416,74 @@ export class Cartesian { var self = this; - var updateHandler = (cell) => { - - var gridCells = cell[0]; - - gridCells.forEach((cellNode) => { - var cell = d3.select(cellNode); - var frames = cell.data(); - frames.reduce( - (units, frame) => { - var mapper; - var frameId = frame.hash(); - if (frame.key) { - - var coordX = self.x(frame.key[self.x.dim]); - var coordY = self.y(frame.key[self.y.dim]); - - var xDomain = self.x.domain(); - var yDomain = self.y.domain(); - - var xPart = self.W / xDomain.length; - var yPart = self.H / yDomain.length; - - mapper = (unit, i) => { - unit.options = { - uid: frameId + i, - frameId: frameId, - container: cell, - containerWidth: self.W, - containerHeight: self.H, - left: coordX - xPart / 2, - top: coordY - yPart / 2, - width: xPart, - height: yPart - }; - return unit; - }; - } else { - mapper = (unit, i) => { - unit.options = { - uid: frameId + i, - container: cell, - containerWidth: self.W, - containerHeight: self.H, - left: 0, - top: 0, - width: self.W, - height: self.H - }; - return unit; - }; - } + var updateCellLayers = (cellId, cell, frame) => { + + var mapper; + var frameId = frame.hash(); + if (frame.key) { + + var coordX = self.x(frame.key[self.x.dim]); + var coordY = self.y(frame.key[self.y.dim]); + + var xDomain = self.x.domain(); + var yDomain = self.y.domain(); + + var xPart = self.W / xDomain.length; + var yPart = self.H / yDomain.length; + + mapper = (unit, i) => { + unit.options = { + uid: frameId + i, + frameId: frameId, + container: cell, + containerWidth: self.W, + containerHeight: self.H, + left: coordX - xPart / 2, + top: coordY - yPart / 2, + width: xPart, + height: yPart + }; + return unit; + }; + } else { + mapper = (unit, i) => { + unit.options = { + uid: frameId + i, + frameId: frameId, + container: cell, + containerWidth: self.W, + containerHeight: self.H, + left: 0, + top: 0, + width: self.W, + height: self.H + }; + return unit; + }; + } - frame.units.map(mapper).map((unit) => continuation(unit, frame)); + var continueDrawUnit = function (unit) { + unit.options.container = d3.select(this); + continuation(unit, frame); + }; - return units.concat(frame.units.map(mapper)); - }, - []); - }); + var layers = cell + .selectAll(`.layer_${cellId}`) + .data(frame.units.map(mapper), (unit) => (unit.options.uid + unit.type)); + layers + .exit() + .remove(); + layers + .each(continueDrawUnit); + layers + .enter() + .append('g') + .attr('class', `layer_${cellId}`) + .each(continueDrawUnit); + }; + + var cellFrameIterator = function (cellFrame) { + updateCellLayers(options.frameId, d3.select(this), cellFrame); }; var cells = this @@ -484,12 +494,12 @@ export class Cartesian { .exit() .remove(); cells - .call(updateHandler); + .each(cellFrameIterator); cells .enter() .append('g') .attr('class', (d) => (`${CSS_PREFIX}cell cell parent-frame-${options.frameId} frame-${d.hash()}`)) - .call(updateHandler); + .each(cellFrameIterator); } _fnDrawDimAxis(container, x, AXIS_POSITION, size, frameId, S) { From 7031b596812fb99a00c0306f6aa4716eb608c70a Mon Sep 17 00:00:00 2001 From: konstantin Date: Mon, 2 Mar 2015 19:10:57 +0300 Subject: [PATCH 33/93] rework interval drawing --- examples/gpl.html | 281 ++++++++++++++++------------ examples/streaming.html | 280 +++++++++++++++++---------- src/elements/element.interval.fn.js | 99 ++++++++++ src/elements/element.interval.js | 72 +++---- src/elements/interval.js | 142 +++----------- 5 files changed, 507 insertions(+), 367 deletions(-) create mode 100644 src/elements/element.interval.fn.js diff --git a/examples/gpl.html b/examples/gpl.html index 3631f7c21..956152f0d 100644 --- a/examples/gpl.html +++ b/examples/gpl.html @@ -2,7 +2,7 @@ - + @@ -231,11 +231,15 @@ return {equation: [lastvalue], points: results, string: "" + lastvalue}; }, - loess: function(data) { + loess: function (data) { //adapted from the LoessInterpolator in org.apache.commons.math function loess_pairs(pairs, bandwidth) { - var xval = pairs.map(function(pair){return pair[0]}); - var yval = pairs.map(function(pair){return pair[1]}); + var xval = pairs.map(function (pair) { + return pair[0] + }); + var yval = pairs.map(function (pair) { + return pair[1] + }); var res = loess(xval, yval, bandwidth); return xval.map(function (x, i) { return [x, res[i]]; @@ -259,7 +263,7 @@ if (i > 0) { if (right < xval.length - 1 && - xval[right+1] - xval[i] < xval[i] - xval[left]) { + xval[right + 1] - xval[i] < xval[i] - xval[left]) { left++; right++; } @@ -277,8 +281,7 @@ var sumX = 0, sumXSquared = 0, sumY = 0, sumXY = 0; var k = left; - while(k <= right) - { + while (k <= right) { var xk = xval[k]; var yk = yval[k]; var dist; @@ -363,30 +366,51 @@ }, '/': { dims: { - story : {type: 'category'}, - bug : {type: 'category'}, - project : {type: 'category'}, - team : {type: 'category'}, - count : {type: 'measure'}, - date : {type: 'measure'} + story: {type: 'category'}, + bug: {type: 'category'}, + project: {type: 'category'}, + team: {type: 'category'}, + count: {type: 'measure'}, + date: {type: 'measure'} }, data: [ - {project: 'TP3', team: 'alpha', story: 'A1', bug: 'ISSUE1', count: 10, date: new Date('2014-01-05')}, - {project: 'tau', team: 'xbeta', story: 'B1', bug: 'ISSUE2', count: 15, date: new Date('2014-10-05')}, - {project: 'tau', team: 'xbeta', story: 'A1', bug: 'ISSUE2', count: 15, date: new Date('2014-10-05')}, + { + project: 'TP3', + team: 'alpha', + story: 'A1', + bug: 'ISSUE1', + count: 10, + date: new Date('2014-01-05') + }, + { + project: 'tau', + team: 'xbeta', + story: 'B1', + bug: 'ISSUE2', + count: 15, + date: new Date('2014-10-05') + }, + { + project: 'tau', + team: 'xbeta', + story: 'A1', + bug: 'ISSUE2', + count: 15, + date: new Date('2014-10-05') + }, ] } }, trans: { where: function (data, tuple) { - var predicates = _.map(tuple, function(v, k) { - return function(row) { + var predicates = _.map(tuple, function (v, k) { + return function (row) { return (row[k] === v); } }); - return _(data).filter(function(row) { - return _.every(predicates, function(p) { + return _(data).filter(function (row) { + return _.every(predicates, function (p) { return p(row); }) }); @@ -394,13 +418,14 @@ }, scales: { - 'size:default' : {type: 'size', source: '?', mid: 5}, - 'color:default' : {type: 'color', source: '?', brewer: null}, - 'story' : {type: 'ordinal', source: '/', dim: 'story'}, - 'bug' : {type: 'ordinal', source: '/', dim: 'bug'}, - 'count' : {type: 'linear', source: '/', dim: 'count', autoScale: true, min: 0, max: 20}, - 'proj' : {type: 'ordinal', source: '/', dim: 'project'}, - 'team' : {type: 'ordinal', source: '/', dim: 'team'} + 'size:default': {type: 'size', source: '?', mid: 5}, + 'color:default': {type: 'color', source: '?', brewer: null}, + 'colorDim': {type: 'color', source: '/', dim: 'team'}, + 'story': {type: 'ordinal', source: '/', dim: 'story'}, + 'bug': {type: 'ordinal', source: '/', dim: 'bug'}, + 'count': {type: 'linear', source: '/', dim: 'count', autoScale: true, min: 0, max: 20}, + 'proj': {type: 'ordinal', source: '/', dim: 'project'}, + 'team': {type: 'ordinal', source: '/', dim: 'team'} }, unit: { @@ -416,8 +441,8 @@ guide: { padding: {l: 60, r: 0, t: 0, b: 60}, showGridLines: '', - x: {padding: 40, label: {text: 'project'}, cssClass:'x axis facet-axis'}, - y: {padding: 40, label: {text: 'team'}, cssClass:'y axis facet-axis'} + x: {padding: 40, label: {text: 'project'}, cssClass: 'x axis facet-axis'}, + y: {padding: 40, label: {text: 'team'}, cssClass: 'y axis facet-axis'} }, units: [ { @@ -439,9 +464,11 @@ type: 'INTERVAL', x: 'story', y: 'count', - color:'team', + color: 'colorDim', expression: { source: '/', + operator:'groupBy', + params:['team'], inherit: true } } @@ -466,21 +493,21 @@ }, '/': { dims: { - xdate : {type: 'measure'}, - count : {type: 'measure'} + xdate: {type: 'measure'}, + count: {type: 'measure'} }, data: [ - {xdate: 1, count: 31 }, - {xdate: 2, count: 27 }, - {xdate: 3, count: 22 }, - {xdate: 4, count: 13 }, - {xdate: 5, count: 9 }, - {xdate: 6, count: 5 }, - {xdate: 7, count: 1 }, - {xdate: 8, count: -3 }, - {xdate: 9, count: -5 }, - {xdate: 10, count: -7 }, - {xdate: 11, count: -9 }, + {xdate: 1, count: 31}, + {xdate: 2, count: 27}, + {xdate: 3, count: 22}, + {xdate: 4, count: 13}, + {xdate: 5, count: 9}, + {xdate: 6, count: 5}, + {xdate: 7, count: 1}, + {xdate: 8, count: -3}, + {xdate: 9, count: -5}, + {xdate: 10, count: -7}, + {xdate: 11, count: -9}, {xdate: 12, count: -11}, {xdate: 13, count: -17}, {xdate: 14, count: -27}, @@ -491,19 +518,19 @@ trans: { where: function (data, tuple) { - var predicates = _.map(tuple, function(v, k) { - return function(row) { + var predicates = _.map(tuple, function (v, k) { + return function (row) { return (row[k] === v); } }); - return _(data).filter(function(row) { - return _.every(predicates, function(p) { + return _(data).filter(function (row) { + return _.every(predicates, function (p) { return p(row); }) }); }, - 'regression': function(data, props) { + 'regression': function (data, props) { var x = props.x; var y = props.y; @@ -523,7 +550,7 @@ // .filter(function (p) { // return ((minY <= p[1]) && (p[1] <= maxY)); // }) - .map(function(p) { + .map(function (p) { var item = {}; item[x] = p[0]; item[y] = p[1]; @@ -532,7 +559,7 @@ .value(); }, - 'x-limit': function(data, props) { + 'x-limit': function (data, props) { var x = props.x; var y = props.y; @@ -549,7 +576,7 @@ return [low, top]; }, - 'y-limit': function(data, props) { + 'y-limit': function (data, props) { var x = props.x; var y = props.y; @@ -568,13 +595,13 @@ }, scales: { - 'size:default' : {type: 'size', name: 'DefSize', source: '?', mid: 5}, - 'color:default' : {type: 'color', name: 'DefColor', source: '?', brewer: null}, - 'count' : {type: 'linear', source: '/', dim: 'count', autoScale: true}, - 'xdate' : {type: 'linear', source: '/', dim: 'xdate'}, + 'size:default': {type: 'size', name: 'DefSize', source: '?', mid: 5}, + 'color:default': {type: 'color', name: 'DefColor', source: '?', brewer: null}, + 'count': {type: 'linear', source: '/', dim: 'count', autoScale: true}, + 'xdate': {type: 'linear', source: '/', dim: 'xdate'}, - 'annotation1' : {type: 'color', source: '/', dim: 'count', brewer: ['color20-2']}, - 'annotation2' : {type: 'color', source: '/', dim: 'count', brewer: ['color20-5']} + 'annotation1': {type: 'color', source: '/', dim: 'count', brewer: ['color20-2']}, + 'annotation2': {type: 'color', source: '/', dim: 'count', brewer: ['color20-5']} }, unit: { @@ -644,13 +671,13 @@ gpl.renderTo('#trendline'); var iteration = 0; - var updateData = function() { + var updateData = function () { if (++iteration > 100) { return; } - facetSpec.sources['/'].data.push({ + facetSpec.sources['/'].data.push({ xdate: parseInt(Math.random() * 10), count: parseInt(Math.random() * 10) + parseInt(Math.random() * 10) * (parseInt(Math.random() * 10) > 5 ? -1 : 1) }); @@ -675,8 +702,8 @@ '$': { dims: { - x : {type: 'category'}, - y : {type: 'category'} + x: {type: 'category'}, + y: {type: 'category'} }, data: [ {x: 1, y: 1}, @@ -687,48 +714,55 @@ 'o': { dims: { - label : {type: 'category'}, - value : {type: 'measure'} + label: {type: 'category'}, + value: {type: 'measure'} }, data: [ - {"label":"Category A", "value":20}, - {"label":"Category B", "value":50}, - {"label":"Category C", "value":30} + {"label": "Category A", "value": 20}, + {"label": "Category B", "value": 50}, + {"label": "Category C", "value": 30} ] }, '/': { dims: { - story : {type: 'category'}, - bug : {type: 'category'}, - project : {type: 'category'}, - team : {type: 'category'}, - count : {type: 'measure'}, - date : {type: 'measure'} + story: {type: 'category'}, + bug: {type: 'category'}, + project: {type: 'category'}, + team: {type: 'category'}, + count: {type: 'measure'}, + date: {type: 'measure'} }, data: [ - {project: 'TP3', team: 'alpha', story: 'A1', bug: 'ISSUE1', count: 10, date: new Date('2014-01-05')}, + { + project: 'TP3', + team: 'alpha', + story: 'A1', + bug: 'ISSUE1', + count: 10, + date: new Date('2014-01-05') + }, {project: 'tau', team: 'xbeta', story: 'B1', bug: 'ISSUE2', count: 15, date: new Date('2014-10-05')} ] }, '@': { dims: { - xdate : {type: 'measure'}, - count : {type: 'measure'} + xdate: {type: 'measure'}, + count: {type: 'measure'} }, data: [ - {xdate: 1, count: 31 }, - {xdate: 2, count: 27 }, - {xdate: 3, count: 22 }, - {xdate: 4, count: 13 }, - {xdate: 5, count: 9 }, - {xdate: 6, count: 5 }, - {xdate: 7, count: 1 }, - {xdate: 8, count: -3 }, - {xdate: 9, count: -5 }, - {xdate: 10, count: -7 }, - {xdate: 11, count: -9 }, + {xdate: 1, count: 31}, + {xdate: 2, count: 27}, + {xdate: 3, count: 22}, + {xdate: 4, count: 13}, + {xdate: 5, count: 9}, + {xdate: 6, count: 5}, + {xdate: 7, count: 1}, + {xdate: 8, count: -3}, + {xdate: 9, count: -5}, + {xdate: 10, count: -7}, + {xdate: 11, count: -9}, {xdate: 12, count: -11}, {xdate: 13, count: -17}, {xdate: 14, count: -27}, @@ -739,19 +773,19 @@ trans: { where: function (data, tuple) { - var predicates = _.map(tuple, function(v, k) { - return function(row) { + var predicates = _.map(tuple, function (v, k) { + return function (row) { return (row[k] === v); } }); - return _(data).filter(function(row) { - return _.every(predicates, function(p) { + return _(data).filter(function (row) { + return _.every(predicates, function (p) { return p(row); }) }); }, - 'regression': function(sVal, props) { + 'regression': function (sVal, props) { var x = props.x; var y = props.y; @@ -768,7 +802,7 @@ .sortBy(function (p) { return p[0]; }) - .map(function(p) { + .map(function (p) { var item = {}; item[x] = p[0]; item[y] = p[1]; @@ -777,7 +811,7 @@ .value(); }, - 'x-limit': function(data, props) { + 'x-limit': function (data, props) { var x = props.x; var y = props.y; @@ -794,7 +828,7 @@ return [low, top]; }, - 'y-limit': function(data, props) { + 'y-limit': function (data, props) { var x = props.x; var y = props.y; @@ -813,26 +847,35 @@ }, scales: { - 'size:default' : {type: 'size', name: 'DefSize', fitToFrame: false, source: '?', mid: 5}, - 'color:default' : {type: 'color', name: 'DefColor', fitToFrame: false, source: '?', brewer: null}, - 'story' : {type: 'ordinal', name: 'S', fitToFrame: false, source: '/', dim: 'story'}, - 'bug' : {type: 'ordinal', name: 'B', fitToFrame: false, source: '/', dim: 'bug'}, - 'count' : {type: 'linear', name: 'C', fitToFrame: false, source: '/', dim: 'count', autoScale: true, min: 0, max: 20}, - 'proj' : {type: 'ordinal', name: 'P', fitToFrame: false, source: '/', dim: 'project'}, - 'team' : {type: 'ordinal', name: 'T', fitToFrame: false, source: '/', dim: 'team'}, - - 'xScale' : {type: 'ordinal', source: '$', dim: 'x'}, - 'yScale' : {type: 'ordinal', source: '$', dim: 'y'}, - - 'counz' : {type: 'linear', name: 'C', source: '@', dim: 'count', autoScale: true}, - 'xdate' : {type: 'linear', name: 'P', source: '@', dim: 'xdate'}, - - 'annotation1' : {type: 'color', name: 'limit', source: '@', dim: 'count', brewer: ['color20-2']}, - 'annotation2' : {type: 'color', name: 'limit', source: '@', dim: 'count', brewer: ['color20-5']}, - - 'label' : {type: 'value', source: 'o', dim: 'label'}, - 'piePortion' : {type: 'value', source: 'o', dim: 'value'}, - 'label_color' : {type: 'color', source: 'o', dim: 'label'} + 'size:default': {type: 'size', name: 'DefSize', fitToFrame: false, source: '?', mid: 5}, + 'color:default': {type: 'color', name: 'DefColor', fitToFrame: false, source: '?', brewer: null}, + 'story': {type: 'ordinal', name: 'S', fitToFrame: false, source: '/', dim: 'story'}, + 'bug': {type: 'ordinal', name: 'B', fitToFrame: false, source: '/', dim: 'bug'}, + 'count': { + type: 'linear', + name: 'C', + fitToFrame: false, + source: '/', + dim: 'count', + autoScale: true, + min: 0, + max: 20 + }, + 'proj': {type: 'ordinal', name: 'P', fitToFrame: false, source: '/', dim: 'project'}, + 'team': {type: 'ordinal', name: 'T', fitToFrame: false, source: '/', dim: 'team'}, + + 'xScale': {type: 'ordinal', source: '$', dim: 'x'}, + 'yScale': {type: 'ordinal', source: '$', dim: 'y'}, + + 'counz': {type: 'linear', name: 'C', source: '@', dim: 'count', autoScale: true}, + 'xdate': {type: 'linear', name: 'P', source: '@', dim: 'xdate'}, + + 'annotation1': {type: 'color', name: 'limit', source: '@', dim: 'count', brewer: ['color20-2']}, + 'annotation2': {type: 'color', name: 'limit', source: '@', dim: 'count', brewer: ['color20-5']}, + + 'label': {type: 'value', source: 'o', dim: 'label'}, + 'piePortion': {type: 'value', source: 'o', dim: 'value'}, + 'label_color': {type: 'color', source: 'o', dim: 'label'} }, unit: { @@ -861,8 +904,8 @@ }, guide: { showGridLines: '', - x: {hide:true}, - y: {hide:true} + x: {hide: true}, + y: {hide: true} }, units: [ { @@ -917,8 +960,8 @@ x: 'count', y: 'story', expression: { - source:'/', - inherit:true + source: '/', + inherit: true } } ] @@ -936,7 +979,7 @@ type: 'RECT', x: 'xdate', y: 'counz', - expression: {source:'@', inherit:false}, + expression: {source: '@', inherit: false}, guide: { x: {label: {text: 'XDATE'}}, y: {label: {text: 'COUNT'}} diff --git a/examples/streaming.html b/examples/streaming.html index 431384506..e455a07ea 100644 --- a/examples/streaming.html +++ b/examples/streaming.html @@ -27,7 +27,7 @@ .color-us { stroke: blue; - fill:blue; + fill: blue; } .color-bug { @@ -90,135 +90,217 @@
diff --git a/src/elements/element.interval.fn.js b/src/elements/element.interval.fn.js new file mode 100644 index 000000000..5540ddc55 --- /dev/null +++ b/src/elements/element.interval.fn.js @@ -0,0 +1,99 @@ +import {utilsDraw} from '../utils/utils-draw'; +var getSizesParams = (params) => { + var countDomainValue = params.domain().length; + var countCategory = params.categoryLength; + var tickWidth = params.size / countDomainValue; + var intervalWidth = tickWidth / (countCategory + 1); + return { + tickWidth, + intervalWidth, + offsetCategory: intervalWidth + }; +}; +var isMeasure = (dim)=> dim.dimType === 'measure'; +var flipHub = { + NORM: ({colorScale, node, xScale, yScale, colorIndexScale, width, height, defaultSizeParams}) => { + let minimalHeight = 1; + let yMin = Math.min(...yScale.domain()); + let isYNumber = !isNaN(yMin); + let startValue = (!isYNumber || (yMin <= 0)) ? 0 : yMin; + let isXNumber = isMeasure(node.x); + + let {tickWidth, intervalWidth, offsetCategory} = isXNumber ? + defaultSizeParams : + getSizesParams({ + domain: xScale.domain, + categoryLength: colorIndexScale.count(), + size: width + }); + + let calculateX = (d) => xScale(d[node.x.scaleDim]) - (tickWidth / 2); + let calculateY = isYNumber ? + ((d) => { + var valY = d[node.y.scaleDim]; + var dotY = yScale(Math.max(startValue, valY)); + var h = Math.abs(yScale(valY) - yScale(startValue)); + var isTooSmall = (h < minimalHeight); + return (isTooSmall && (valY > 0)) ? (dotY - minimalHeight) : dotY; + }) : + ((d) => yScale(d[node.y.scaleDim])); + + let calculateWidth = (d) => intervalWidth; + let calculateHeight = isYNumber ? + ((d) => { + var valY = d[node.y.scaleDim]; + var h = Math.abs(yScale(valY) - yScale(startValue)); + return (valY === 0) ? h : Math.max(minimalHeight, h); + }) : + ((d) => (height - yScale(d[node.y.scaleDim]))); + + let calculateTranslate = (d) => + utilsDraw.translate(colorIndexScale(d) * offsetCategory + offsetCategory / 2, 0); + + return {colorScale, calculateX, calculateY, calculateWidth, calculateHeight, calculateTranslate}; + }, + + FLIP: ({colorScale, node, xScale, yScale, colorIndexScale, width, height, defaultSizeParams}) => { + let minimalHeight = 1; + let xMin = Math.min(...xScale.domain()); + let isXNumber = !isNaN(xMin); + let startValue = (!isXNumber || (xMin <= 0)) ? 0 : xMin; + let isYNumber = isMeasure(node.y); + + let {tickWidth, intervalWidth, offsetCategory} = isYNumber ? + defaultSizeParams : + getSizesParams({ + domain: yScale.domain, + categoryLength: colorIndexScale.count(), + size: height + }); + + let calculateX = isXNumber ? + ((d) => { + var valX = d[node.x.scaleDim]; + var h = Math.abs(xScale(valX) - xScale(startValue)); + var dotX = xScale(Math.min(startValue, valX)); + var delta = (h - minimalHeight); + var offset = (valX > 0) ? (minimalHeight + delta) : ((valX < 0) ? (0 - minimalHeight) : 0); + + var isTooSmall = (delta < 0); + return (isTooSmall) ? (dotX + offset) : (dotX); + }) : + 0; + let calculateY = (d) => yScale(d[node.y.scaleDim]) - (tickWidth / 2); + let calculateWidth = isXNumber ? + ((d) => { + var valX = d[node.x.scaleDim]; + var h = Math.abs(xScale(valX) - xScale(startValue)); + return (valX === 0) ? h : Math.max(minimalHeight, h); + }) : + ((d) => xScale(d[node.x.scaleDim])); + let calculateHeight = (d) => intervalWidth; + let calculateTranslate = (d) => + utilsDraw.translate(0, colorIndexScale(d) * offsetCategory + offsetCategory / 2); + + return {colorScale, calculateX, calculateY, calculateWidth, calculateHeight, calculateTranslate}; + } +}; + +export {flipHub}; \ No newline at end of file diff --git a/src/elements/element.interval.js b/src/elements/element.interval.js index 9f799fcaa..2680d338c 100644 --- a/src/elements/element.interval.js +++ b/src/elements/element.interval.js @@ -1,5 +1,6 @@ import {CSS_PREFIX} from '../const'; -import {interval} from './interval'; +import {flipHub} from './element.interval.fn'; +import {interval, drawInterval} from './interval'; export class Interval { @@ -26,37 +27,44 @@ export class Interval { var config = this.config; var xScale = this.xScale; var yScale = this.yScale; - var color = this.color; - var sScale = this.size; - - return frames.map((frame) => { - // frame.take(); - var node = { - options: { - container: canvas, - xScale, - yScale, - color, - width:config.options.width, - height:config.options.height - }, - x: xScale, - y: yScale, - color: color, - groupBy() { - return d3.nest() - .key(function (d) { - return d[color.scaleDim]; - }) - .entries(frame.take()); - }, - partition() { - }, - source() { - } - }; - interval(node); - return frame; + var colorScale = this.color; + var node = { + options: { + container: canvas, + xScale, + yScale, + color: colorScale, + width: config.options.width, + height: config.options.height + }, + x: xScale, + y: yScale, + color: colorScale + }; + var method = flipHub[node.flip ? 'FLIP' : 'NORM']; + var colorIndexScale = (d) => { + return _.findIndex(domain, (value)=> { + return value === d.key[colorScale.scaleDim]; + }); + }; + // colorScale.scaleDim = node.color.scaleDim; + var domain = colorScale.domain(); + colorIndexScale.count = () => domain.length; + + var params = method({ + node, + xScale, + yScale, + colorScale, + colorIndexScale, + width: config.options.width, + height: config.options.height, + defaultSizeParams: { + tickWidth: 5, + intervalWidth: 5, + offsetCategory: 0 + } }); + drawInterval(params, canvas, frames.map((fr)=>({key: fr.key, values: fr.data}))); } } \ No newline at end of file diff --git a/src/elements/interval.js b/src/elements/interval.js index c61366680..37c7d0c4d 100644 --- a/src/elements/interval.js +++ b/src/elements/interval.js @@ -1,108 +1,8 @@ -import {utilsDraw} from '../utils/utils-draw'; import {CSS_PREFIX} from '../const'; +import {flipHub} from './element.interval.fn'; const BAR_GROUP = 'i-role-bar-group'; -var isMeasure = (dim) => dim.dimType === 'measure'; - -var getSizesParams = (params) => { - var countDomainValue = params.domain().length; - var countCategory = params.categoryLength; - var tickWidth = params.size / countDomainValue; - var intervalWidth = tickWidth / (countCategory + 1); - return { - tickWidth, - intervalWidth, - offsetCategory: intervalWidth - }; -}; - -var flipHub = { - - NORM: (node, xScale, yScale, colorIndexScale, width, height, defaultSizeParams) => { - let minimalHeight = 1; - let yMin = Math.min(...yScale.domain()); - let isYNumber = !isNaN(yMin); - let startValue = (!isYNumber || (yMin <= 0)) ? 0 : yMin; - let isXNumber = isMeasure(node.x); - - let {tickWidth, intervalWidth, offsetCategory} = isXNumber ? - defaultSizeParams : - getSizesParams({ - domain: xScale.domain, - categoryLength: colorIndexScale.count(), - size: width - }); - - let calculateX = (d) => xScale(d[node.x.scaleDim]) - (tickWidth / 2); - let calculateY = isYNumber ? - ((d) => { - var valY = d[node.y.scaleDim]; - var dotY = yScale(Math.max(startValue, valY)); - var h = Math.abs(yScale(valY) - yScale(startValue)); - var isTooSmall = (h < minimalHeight); - return (isTooSmall && (valY > 0)) ? (dotY - minimalHeight) : dotY; - }) : - ((d) => yScale(d[node.y.scaleDim])); - - let calculateWidth = (d) => intervalWidth; - let calculateHeight = isYNumber ? - ((d) => { - var valY = d[node.y.scaleDim]; - var h = Math.abs(yScale(valY) - yScale(startValue)); - return (valY === 0) ? h : Math.max(minimalHeight, h); - }) : - ((d) => (height - yScale(d[node.y.scaleDim]))); - - let calculateTranslate = (d) => - utilsDraw.translate(colorIndexScale(d) * offsetCategory + offsetCategory / 2, 0); - - return {calculateX, calculateY, calculateWidth, calculateHeight, calculateTranslate}; - }, - - FLIP: (node, xScale, yScale, colorIndexScale, width, height, defaultSizeParams) => { - let minimalHeight = 1; - let xMin = Math.min(...xScale.domain()); - let isXNumber = !isNaN(xMin); - let startValue = (!isXNumber || (xMin <= 0)) ? 0 : xMin; - let isYNumber = isMeasure(node.y); - - let {tickWidth, intervalWidth, offsetCategory} = isYNumber ? - defaultSizeParams : - getSizesParams({ - domain: yScale.domain, - categoryLength: colorIndexScale.count(), - size: height - }); - - let calculateX = isXNumber ? - ((d) => { - var valX = d[node.x.scaleDim]; - var h = Math.abs(xScale(valX) - xScale(startValue)); - var dotX = xScale(Math.min(startValue, valX)); - var delta = (h - minimalHeight); - var offset = (valX > 0) ? (minimalHeight + delta) : ((valX < 0) ? (0 - minimalHeight) : 0); - - var isTooSmall = (delta < 0); - return (isTooSmall) ? (dotX + offset) : (dotX); - }) : - 0; - let calculateY = (d) => yScale(d[node.y.scaleDim]) - (tickWidth / 2); - let calculateWidth = isXNumber ? - ((d) => { - var valX = d[node.x.scaleDim]; - var h = Math.abs(xScale(valX) - xScale(startValue)); - return (valX === 0) ? h : Math.max(minimalHeight, h); - }) : - ((d) => xScale(d[node.x.scaleDim])); - let calculateHeight = (d) => intervalWidth; - let calculateTranslate = (d) => - utilsDraw.translate(0, colorIndexScale(d) * offsetCategory + offsetCategory / 2); - - return {calculateX, calculateY, calculateWidth, calculateHeight, calculateTranslate}; - } -}; - var interval = function (node) { var options = node.options; @@ -129,32 +29,42 @@ var interval = function (node) { return index; }; - + colorScale.scaleDim = node.color.scaleDim; colorIndexScale.count = () => allCategories.length; - var {calculateX, calculateY, calculateWidth, calculateHeight, calculateTranslate} = method( + var params = method({ node, xScale, yScale, + colorScale, colorIndexScale, - options.width, - options.height, - { + width: options.width, + height: options.height, + defaultSizeParams: { tickWidth: 5, intervalWidth: 5, offsetCategory: 0 } - ); - + }); + drawInterval(params, options.container, categories); +}; +function drawInterval({ + calculateX, + calculateY, + colorScale, + calculateWidth, + calculateHeight, + calculateTranslate + }, + container, + data) { var updateBar = function () { return this .attr('height', calculateHeight) .attr('width', calculateWidth) -// jscs:disable - .attr('class', (d) =>{ - return (`i-role-element i-role-datum bar ${CSS_PREFIX}bar ${colorScale(d[node.color.scaleDim])}`); + .attr('class', (d) => { + return `i-role-element i-role-datum bar ${CSS_PREFIX}bar ${colorScale(d[colorScale.scaleDim])}`; }) -// jscs:enable .attr('x', calculateX) .attr('y', calculateY); }; @@ -168,12 +78,10 @@ var interval = function (node) { bars.exit().remove(); }; -// jscs:disable disallowSpacesInsideParentheses - var elements = options.container.selectAll(`.${BAR_GROUP}`).data(categories); -// jscs:enable disallowSpacesInsideParentheses + var elements = container.selectAll(`.${BAR_GROUP}`).data(data); elements.call(updateBarContainer); elements.enter().append('g').call(updateBarContainer); elements.exit().remove(); -}; +} -export {interval}; \ No newline at end of file +export {interval, drawInterval}; \ No newline at end of file From ed14dd602ba4e8d9cf1348dbb20d86d0a2d6cdb1 Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Mon, 2 Mar 2015 20:50:02 +0300 Subject: [PATCH 34/93] extracting d3 axis decorators --- src/elements/coords.cartesian.js | 301 ++++++++++++++++--------------- 1 file changed, 152 insertions(+), 149 deletions(-) diff --git a/src/elements/coords.cartesian.js b/src/elements/coords.cartesian.js index 4fc0a3ad1..6c861169c 100644 --- a/src/elements/coords.cartesian.js +++ b/src/elements/coords.cartesian.js @@ -5,10 +5,6 @@ import {FormatterRegistry} from '../formatter-registry'; import {default as _} from 'underscore'; import {default as d3} from 'd3'; -var translate = (left, top) => 'translate(' + left + ',' + top + ')'; -var rotate = (angle) => 'rotate(' + angle + ')'; -var getOrientation = (scaleOrient) => _.contains(['bottom', 'top'], scaleOrient.toLowerCase()) ? 'h' : 'v'; - var d3getComputedTextLength = _.memoize( (d3Text) => d3Text.node().getComputedTextLength(), (d3Text) => d3Text.node().textContent.length); @@ -112,105 +108,91 @@ var wrapText = (textNode, widthLimit, linesLimit, tickLabelFontHeight, isY, getC }); }; -var decorateAxisTicks = (nodeScale, x, size) => { +var d3_decorator_prettify_categorical_axis_ticks = (nodeAxis, size, isHorizontal) => { - var selection = nodeScale.selectAll('.tick line'); + var selection = nodeAxis.selectAll('.tick line'); + if (selection.empty()) { + return; + } var sectorSize = size / selection[0].length; var offsetSize = sectorSize / 2; - var isHorizontal = (getOrientation(x.guide.scaleOrient) === 'h'); + var key = (isHorizontal) ? 'x' : 'y'; + var val = (isHorizontal) ? offsetSize : (-offsetSize); - if (x.scaleType === 'ordinal' || x.scaleType === 'period') { + selection.attr(key + '1', val).attr(key + '2', val); +}; - var key = (isHorizontal) ? 'x' : 'y'; - var val = (isHorizontal) ? offsetSize : (-offsetSize); +var d3_decorator_fix_horizontal_axis_ticks_overflow = (axisNode) => { - selection.attr(key + '1', val).attr(key + '2', val); + var timeTicks = axisNode.selectAll('.tick')[0]; + if (timeTicks.length < 2) { + return; } -}; -var fixAxisTickOverflow = (nodeScale, x) => { + var tick0 = parseFloat(timeTicks[0].attributes.transform.value.replace('translate(', '')); + var tick1 = parseFloat(timeTicks[1].attributes.transform.value.replace('translate(', '')); - var isHorizontal = (getOrientation(x.guide.scaleOrient) === 'h'); + var tickStep = tick1 - tick0; - if (isHorizontal && (x.scaleType === 'time')) { - var timeTicks = nodeScale.selectAll('.tick')[0]; - if (timeTicks.length < 2) { - return; + var maxTextLn = 0; + var iMaxTexts = -1; + var timeTexts = axisNode.selectAll('.tick text')[0]; + timeTexts.forEach((textNode, i) => { + var innerHTML = textNode.textContent || ''; + var textLength = innerHTML.length; + if (textLength > maxTextLn) { + maxTextLn = textLength; + iMaxTexts = i; } + }); - var tick0 = parseFloat(timeTicks[0].attributes.transform.value.replace('translate(', '')); - var tick1 = parseFloat(timeTicks[1].attributes.transform.value.replace('translate(', '')); - - var tickStep = tick1 - tick0; - - var maxTextLn = 0; - var iMaxTexts = -1; - var timeTexts = nodeScale.selectAll('.tick text')[0]; - timeTexts.forEach((textNode, i) => { - var innerHTML = textNode.textContent || ''; - var textLength = innerHTML.length; - if (textLength > maxTextLn) { - maxTextLn = textLength; - iMaxTexts = i; - } - }); - - if (iMaxTexts >= 0) { - var rect = timeTexts[iMaxTexts].getBoundingClientRect(); - // 2px from each side - if ((tickStep - rect.width) < 8) { - nodeScale.classed({'graphical-report__d3-time-overflown': true}); - } + if (iMaxTexts >= 0) { + var rect = timeTexts[iMaxTexts].getBoundingClientRect(); + // 2px from each side + if ((tickStep - rect.width) < 8) { + axisNode.classed({'graphical-report__d3-time-overflown': true}); } } }; -var fixAxisBottomLine = (nodeScale, x, size) => { - - var selection = nodeScale.selectAll('.tick line'); +var d3_decorator_fix_axis_bottom_line = (axisNode, size, isContinuesScale) => { - var isHorizontal = (getOrientation(x.guide.scaleOrient) === 'h'); - - if (isHorizontal) { + var selection = axisNode.selectAll('.tick line'); + if (selection.empty()) { return; } - var doApply = false; var tickOffset = -1; - if (x.scaleType === 'time') { - doApply = true; + if (isContinuesScale) { tickOffset = 0; - } else if (x.scaleType === 'ordinal' || x.scaleType === 'period') { - doApply = true; + } else { var sectorSize = size / selection[0].length; var offsetSize = sectorSize / 2; tickOffset = (-offsetSize); } - if (doApply) { - var tickGroupClone = nodeScale.select('.tick').node().cloneNode(true); - nodeScale - .append(() => tickGroupClone) - .attr('transform', translate(0, size - tickOffset)); - } + var tickGroupClone = axisNode.select('.tick').node().cloneNode(true); + axisNode + .append(() => tickGroupClone) + .attr('transform', utilsDraw.translate(0, size - tickOffset)); }; -var decorateAxisLabel = (nodeScale, x) => { - var orient = getOrientation(x.guide.scaleOrient); - var koeff = (orient === 'h') ? 1 : -1; - var labelTextNode = nodeScale +var d3_decorator_prettify_axis_label = (axisNode, guide, isHorizontal) => { + + var koeff = (isHorizontal) ? 1 : -1; + var labelTextNode = axisNode .append('text') - .attr('transform', rotate(x.guide.label.rotate)) - .attr('class', x.guide.label.cssClass) - .attr('x', koeff * x.guide.size * 0.5) - .attr('y', koeff * x.guide.label.padding) - .style('text-anchor', x.guide.label.textAnchor); + .attr('transform', utilsDraw.rotate(guide.rotate)) + .attr('class', guide.cssClass) + .attr('x', koeff * guide.size * 0.5) + .attr('y', koeff * guide.padding) + .style('text-anchor', guide.textAnchor); var delimiter = ' > '; - var tags = x.guide.label.text.split(delimiter); + var tags = guide.text.split(delimiter); var tLen = tags.length; tags.forEach((token, i) => { @@ -227,42 +209,40 @@ var decorateAxisLabel = (nodeScale, x) => { } }); - if (x.guide.label.dock === 'right') { - let box = nodeScale.selectAll('path.domain').node().getBBox(); - labelTextNode.attr('x', (orient === 'h') ? (box.width) : 0); - } else if (x.guide.label.dock === 'left') { - let box = nodeScale.selectAll('path.domain').node().getBBox(); - labelTextNode.attr('x', (orient === 'h') ? 0 : (-box.height)); + if (guide.dock === 'right') { + let box = axisNode.selectAll('path.domain').node().getBBox(); + labelTextNode.attr('x', isHorizontal ? (box.width) : 0); + } else if (guide.dock === 'left') { + let box = axisNode.selectAll('path.domain').node().getBBox(); + labelTextNode.attr('x', isHorizontal ? 0 : (-box.height)); } }; -var decorateTickLabel = (nodeScale, x) => { +var d3_decorator_wrap_tick_label = (nodeScale, guide, isHorizontal) => { - var isHorizontal = (getOrientation(x.guide.scaleOrient) === 'h'); - - var angle = x.guide.rotate; + var angle = guide.rotate; var ticks = nodeScale.selectAll('.tick text'); ticks - .attr('transform', rotate(angle)) - .style('text-anchor', x.guide.textAnchor); + .attr('transform', utilsDraw.rotate(angle)) + .style('text-anchor', guide.textAnchor); if (angle === 90) { var dy = parseFloat(ticks.attr('dy')) / 2; ticks.attr('x', 9).attr('y', 0).attr('dy', `${dy}em`); } - if (x.guide.tickFormatWordWrap) { + if (guide.tickFormatWordWrap) { ticks.call( wrapText, - x.guide.tickFormatWordWrapLimit, - x.guide.tickFormatWordWrapLines, - x.guide.$maxTickTextH, + guide.tickFormatWordWrapLimit, + guide.tickFormatWordWrapLines, + guide.$maxTickTextH, !isHorizontal ); } else { ticks - .call(cutText, x.guide.tickFormatWordWrapLimit); + .call(cutText, guide.tickFormatWordWrapLimit); } }; @@ -350,8 +330,8 @@ export class Cartesian { var innerWidth = options.width - (padding.l + padding.r); var innerHeight = options.height - (padding.t + padding.b); - this.x = this.xScale = fnCreateScale('pos', node.x, [0, innerWidth]); - this.y = this.yScale = fnCreateScale('pos', node.y, [innerHeight, 0]); + this.xScale = fnCreateScale('pos', node.x, [0, innerWidth]); + this.yScale = fnCreateScale('pos', node.y, [innerHeight, 0]); this.W = innerWidth; this.H = innerHeight; @@ -381,8 +361,8 @@ export class Cartesian { node.x.guide = node.guide.x; node.y.guide = node.guide.y; - node.x.guide.size = innerWidth; - node.y.guide.size = innerHeight; + node.x.guide.label.size = innerWidth; + node.y.guide.label.size = innerHeight; options .container @@ -393,10 +373,11 @@ export class Cartesian { if (!node.x.guide.hide) { this._fnDrawDimAxis( - options.container, node.x, + options.container, + node.x, [0, innerHeight + node.guide.x.padding], innerWidth, - options.frameId + 'x', + (`${options.frameId}x`), hashX ); } @@ -407,37 +388,33 @@ export class Cartesian { node.y, [0 - node.guide.y.padding, 0], innerHeight, - options.frameId + 'y', + (`${options.frameId}y`), hashY ); } - this.grid = this._fnDrawGrid(options.container, node, innerHeight, innerWidth, options.frameId, hashX + hashY); - - var self = this; - var updateCellLayers = (cellId, cell, frame) => { var mapper; var frameId = frame.hash(); if (frame.key) { - var coordX = self.x(frame.key[self.x.dim]); - var coordY = self.y(frame.key[self.y.dim]); + var coordX = node.x(frame.key[node.x.dim]); + var coordY = node.y(frame.key[node.y.dim]); - var xDomain = self.x.domain(); - var yDomain = self.y.domain(); + var xDomain = node.x.domain(); + var yDomain = node.y.domain(); - var xPart = self.W / xDomain.length; - var yPart = self.H / yDomain.length; + var xPart = innerWidth / xDomain.length; + var yPart = innerHeight / yDomain.length; mapper = (unit, i) => { unit.options = { uid: frameId + i, frameId: frameId, container: cell, - containerWidth: self.W, - containerHeight: self.H, + containerWidth: innerWidth, + containerHeight: innerHeight, left: coordX - xPart / 2, top: coordY - yPart / 2, width: xPart, @@ -451,12 +428,12 @@ export class Cartesian { uid: frameId + i, frameId: frameId, container: cell, - containerWidth: self.W, - containerHeight: self.H, + containerWidth: innerWidth, + containerHeight: innerHeight, left: 0, top: 0, - width: self.W, - height: self.H + width: innerWidth, + height: innerHeight }; return unit; }; @@ -487,7 +464,7 @@ export class Cartesian { }; var cells = this - .grid + ._fnDrawGrid(options.container, node, innerHeight, innerWidth, options.frameId, hashX + hashY) .selectAll(`.parent-frame-${options.frameId}`) .data(frames, (f) => f.hash()); cells @@ -502,55 +479,63 @@ export class Cartesian { .each(cellFrameIterator); } - _fnDrawDimAxis(container, x, AXIS_POSITION, size, frameId, S) { + _fnDrawDimAxis(container, scale, position, size, frameId, uniqueHash) { - if (x.scaleDim) { + if (scale.scaleDim) { var axisScale = d3.svg .axis() - .scale(x.scaleObj) - .orient(x.guide.scaleOrient); + .scale(scale.scaleObj) + .orient(scale.guide.scaleOrient); - var formatter = FormatterRegistry.get(x.guide.tickFormat, x.guide.tickFormatNullAlias); + var formatter = FormatterRegistry.get(scale.guide.tickFormat, scale.guide.tickFormatNullAlias); if (formatter !== null) { - axisScale.ticks(Math.round(size / x.guide.density)); + axisScale.ticks(Math.round(size / scale.guide.density)); axisScale.tickFormat(formatter); } var axis = container .selectAll('.axis_' + frameId) - .data([S], (x) => x); + .data([uniqueHash], (x) => x); axis.exit() .remove(); axis.enter() .append('g') - .attr('class', x.guide.cssClass + ' axis_' + frameId) - .attr('transform', utilsDraw.translate(...AXIS_POSITION)) - .call(function (selection) { - if (!selection.empty()) { - axisScale.call(this, selection); - decorateAxisTicks(selection, x, size); - decorateTickLabel(selection, x); - decorateAxisLabel(selection, x); - fixAxisTickOverflow(selection, x); + .attr('class', scale.guide.cssClass + ' axis_' + frameId) + .attr('transform', utilsDraw.translate(...position)) + .call(function (refAxisNode) { + if (!refAxisNode.empty()) { + + axisScale.call(this, refAxisNode); + + var isHorizontal = (utilsDraw.getOrientation(scale.guide.scaleOrient) === 'h'); + var prettifyTick = (scale.scaleType === 'ordinal' || scale.scaleType === 'period'); + if (prettifyTick) { + d3_decorator_prettify_categorical_axis_ticks(refAxisNode, size, isHorizontal); + } + + d3_decorator_wrap_tick_label(refAxisNode, scale.guide, isHorizontal); + d3_decorator_prettify_axis_label(refAxisNode, scale.guide.label, isHorizontal); + + if (isHorizontal && (scale.scaleType === 'time')) { + d3_decorator_fix_horizontal_axis_ticks_overflow(refAxisNode); + } } }); } } - _fnDrawGrid(container, node, H, W, frameId, S) { + _fnDrawGrid(container, node, height, width, frameId, uniqueHash) { var grid = container .selectAll('.grid_' + frameId) - .data([S], (x) => x); - + .data([uniqueHash], (x) => x); grid.exit() .remove(); - grid.enter() .append('g') .attr('class', 'grid grid_' + frameId) - .attr('transform', translate(0, 0)) + .attr('transform', utilsDraw.translate(0, 0)) .call((selection) => { if (selection.empty()) { @@ -565,22 +550,29 @@ export class Cartesian { var gridLines = grid.append('g').attr('class', 'grid-lines'); if ((linesOptions.indexOf('x') > -1) && node.x.scaleDim) { - var x = node.x; + let xScale = node.x; var xGridAxis = d3.svg .axis() - .scale(x.scaleObj) - .orient(x.guide.scaleOrient) - .tickSize(H); + .scale(xScale.scaleObj) + .orient(xScale.guide.scaleOrient) + .tickSize(height); - let formatter = FormatterRegistry.get(x.guide.tickFormat); + let formatter = FormatterRegistry.get(xScale.guide.tickFormat); if (formatter !== null) { - xGridAxis.ticks(Math.round(W / x.guide.density)); + xGridAxis.ticks(Math.round(width / xScale.guide.density)); xGridAxis.tickFormat(formatter); } - var xGridLines = gridLines.append('g').attr('class', 'grid-lines-x').call(xGridAxis); + var xGridLines = gridLines + .append('g') + .attr('class', 'grid-lines-x') + .call(xGridAxis); - decorateAxisTicks(xGridLines, x, W); + let isHorizontal = (utilsDraw.getOrientation(xScale.guide.scaleOrient) === 'h'); + let prettifyTick = (xScale.scaleType === 'ordinal' || xScale.scaleType === 'period'); + if (prettifyTick) { + d3_decorator_prettify_categorical_axis_ticks(xGridLines, width, isHorizontal); + } var firstXGridLine = xGridLines.select('g.tick'); if (firstXGridLine.node() && firstXGridLine.attr('transform') !== 'translate(0,0)') { @@ -588,7 +580,7 @@ export class Cartesian { gridLines.node().appendChild(zeroNode); d3.select(zeroNode) .attr('class', 'border') - .attr('transform', translate(0, 0)) + .attr('transform', utilsDraw.translate(0, 0)) .select('line') .attr('x1', 0) .attr('x2', 0); @@ -596,26 +588,37 @@ export class Cartesian { } if ((linesOptions.indexOf('y') > -1) && node.y.scaleDim) { - var y = node.y; + let yScale = node.y; var yGridAxis = d3.svg .axis() - .scale(y.scaleObj) - .orient(y.guide.scaleOrient) - .tickSize(-W); + .scale(yScale.scaleObj) + .orient(yScale.guide.scaleOrient) + .tickSize(-width); - let formatter = FormatterRegistry.get(y.guide.tickFormat); + let formatter = FormatterRegistry.get(yScale.guide.tickFormat); if (formatter !== null) { - yGridAxis.ticks(Math.round(H / y.guide.density)); + yGridAxis.ticks(Math.round(height / yScale.guide.density)); yGridAxis.tickFormat(formatter); } - var yGridLines = gridLines.append('g').attr('class', 'grid-lines-y').call(yGridAxis); + var yGridLines = gridLines + .append('g') + .attr('class', 'grid-lines-y') + .call(yGridAxis); + + let isHorizontal = (utilsDraw.getOrientation(yScale.guide.scaleOrient) === 'h'); + let prettifyTick = (yScale.scaleType === 'ordinal' || yScale.scaleType === 'period'); + if (prettifyTick) { + d3_decorator_prettify_categorical_axis_ticks(yGridLines, height, isHorizontal); + } - decorateAxisTicks(yGridLines, y, H); - fixAxisBottomLine(yGridLines, y, H); + let fixLineScales = ['time', 'ordinal', 'period']; + let fixBottomLine = _.contains(fixLineScales, yScale.scaleType); + if (fixBottomLine) { + d3_decorator_fix_axis_bottom_line(yGridLines, height, (yScale.scaleType === 'time')); + } } - // TODO: make own axes and grid instead of using d3's in such tricky way gridLines.selectAll('text').remove(); } }); From 503ccfbd6e57ff1b672bcfd032b287bc0eda7e01 Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Tue, 3 Mar 2015 12:27:40 +0300 Subject: [PATCH 35/93] extract d3 decorators to separate file --- src/elements/coords.cartesian.js | 253 ++----------------------------- src/utils/d3-decorators.js | 252 ++++++++++++++++++++++++++++++ 2 files changed, 261 insertions(+), 244 deletions(-) create mode 100644 src/utils/d3-decorators.js diff --git a/src/elements/coords.cartesian.js b/src/elements/coords.cartesian.js index 6c861169c..187998c70 100644 --- a/src/elements/coords.cartesian.js +++ b/src/elements/coords.cartesian.js @@ -1,250 +1,15 @@ +import {default as d3} from 'd3'; +import {default as _} from 'underscore'; import {utilsDraw} from '../utils/utils-draw'; import {CSS_PREFIX} from '../const'; -import {utils} from '../utils/utils'; import {FormatterRegistry} from '../formatter-registry'; -import {default as _} from 'underscore'; -import {default as d3} from 'd3'; - -var d3getComputedTextLength = _.memoize( - (d3Text) => d3Text.node().getComputedTextLength(), - (d3Text) => d3Text.node().textContent.length); - -var cutText = (textString, widthLimit, getComputedTextLength) => { - - getComputedTextLength = getComputedTextLength || d3getComputedTextLength; - - textString.each(function () { - var textD3 = d3.select(this); - var tokens = textD3.text().split(/\s+/); - - var stop = false; - var parts = tokens.reduce((memo, t, i) => { - - if (stop) { - return memo; - } - - var text = (i > 0) ? [memo, t].join(' ') : t; - var len = getComputedTextLength(textD3.text(text)); - if (len < widthLimit) { - memo = text; - } else { - var available = Math.floor(widthLimit / len * text.length); - memo = text.substr(0, available - 4) + '...'; - stop = true; - } - - return memo; - - }, ''); - - textD3.text(parts); - }); -}; - -var wrapText = (textNode, widthLimit, linesLimit, tickLabelFontHeight, isY, getComputedTextLength) => { - - getComputedTextLength = getComputedTextLength || d3getComputedTextLength; - - var addLine = (targetD3, text, lineHeight, x, y, dy, lineNumber) => { - var dyNew = (lineNumber * lineHeight) + dy; - return targetD3 - .append('tspan') - .attr('x', x) - .attr('y', y) - .attr('dy', dyNew + 'em') - .text(text); - }; - - textNode.each(function () { - var textD3 = d3.select(this), - tokens = textD3.text().split(/\s+/), - lineHeight = 1.1, // ems - x = textD3.attr('x'), - y = textD3.attr('y'), - dy = parseFloat(textD3.attr('dy')); - - textD3.text(null); - var tempSpan = addLine(textD3, null, lineHeight, x, y, dy, 0); - - var stopReduce = false; - var tokensCount = (tokens.length - 1); - var lines = tokens - .reduce((memo, next, i) => { - - if (stopReduce) { - return memo; - } - - var isLimit = (memo.length === linesLimit) || (i === tokensCount); - var last = memo[memo.length - 1]; - var text = (last !== '') ? (last + ' ' + next) : next; - var tLen = getComputedTextLength(tempSpan.text(text)); - var over = tLen > widthLimit; - - if (over && isLimit) { - var available = Math.floor(widthLimit / tLen * text.length); - memo[memo.length - 1] = text.substr(0, available - 4) + '...'; - stopReduce = true; - } - - if (over && !isLimit) { - memo.push(next); - } - - if (!over) { - memo[memo.length - 1] = text; - } - - return memo; - - }, ['']) - .filter((l) => l.length > 0); - - y = isY ? (-1 * (lines.length - 1) * Math.floor(tickLabelFontHeight * 0.5)) : y; - lines.forEach((text, i) => addLine(textD3, text, lineHeight, x, y, dy, i)); - - tempSpan.remove(); - }); -}; - -var d3_decorator_prettify_categorical_axis_ticks = (nodeAxis, size, isHorizontal) => { - - var selection = nodeAxis.selectAll('.tick line'); - if (selection.empty()) { - return; - } - - var sectorSize = size / selection[0].length; - var offsetSize = sectorSize / 2; - - var key = (isHorizontal) ? 'x' : 'y'; - var val = (isHorizontal) ? offsetSize : (-offsetSize); - - selection.attr(key + '1', val).attr(key + '2', val); -}; - -var d3_decorator_fix_horizontal_axis_ticks_overflow = (axisNode) => { - - var timeTicks = axisNode.selectAll('.tick')[0]; - if (timeTicks.length < 2) { - return; - } - - var tick0 = parseFloat(timeTicks[0].attributes.transform.value.replace('translate(', '')); - var tick1 = parseFloat(timeTicks[1].attributes.transform.value.replace('translate(', '')); - - var tickStep = tick1 - tick0; - - var maxTextLn = 0; - var iMaxTexts = -1; - var timeTexts = axisNode.selectAll('.tick text')[0]; - timeTexts.forEach((textNode, i) => { - var innerHTML = textNode.textContent || ''; - var textLength = innerHTML.length; - if (textLength > maxTextLn) { - maxTextLn = textLength; - iMaxTexts = i; - } - }); - - if (iMaxTexts >= 0) { - var rect = timeTexts[iMaxTexts].getBoundingClientRect(); - // 2px from each side - if ((tickStep - rect.width) < 8) { - axisNode.classed({'graphical-report__d3-time-overflown': true}); - } - } -}; - -var d3_decorator_fix_axis_bottom_line = (axisNode, size, isContinuesScale) => { - - var selection = axisNode.selectAll('.tick line'); - if (selection.empty()) { - return; - } - - var tickOffset = -1; - - if (isContinuesScale) { - tickOffset = 0; - } else { - var sectorSize = size / selection[0].length; - var offsetSize = sectorSize / 2; - tickOffset = (-offsetSize); - } - - var tickGroupClone = axisNode.select('.tick').node().cloneNode(true); - axisNode - .append(() => tickGroupClone) - .attr('transform', utilsDraw.translate(0, size - tickOffset)); -}; - -var d3_decorator_prettify_axis_label = (axisNode, guide, isHorizontal) => { - - var koeff = (isHorizontal) ? 1 : -1; - var labelTextNode = axisNode - .append('text') - .attr('transform', utilsDraw.rotate(guide.rotate)) - .attr('class', guide.cssClass) - .attr('x', koeff * guide.size * 0.5) - .attr('y', koeff * guide.padding) - .style('text-anchor', guide.textAnchor); - - var delimiter = ' > '; - var tags = guide.text.split(delimiter); - var tLen = tags.length; - tags.forEach((token, i) => { - - labelTextNode - .append('tspan') - .attr('class', 'label-token label-token-' + i) - .text(token); - - if (i < (tLen - 1)) { - labelTextNode - .append('tspan') - .attr('class', 'label-token-delimiter label-token-delimiter-' + i) - .text(delimiter); - } - }); - - if (guide.dock === 'right') { - let box = axisNode.selectAll('path.domain').node().getBBox(); - labelTextNode.attr('x', isHorizontal ? (box.width) : 0); - } else if (guide.dock === 'left') { - let box = axisNode.selectAll('path.domain').node().getBBox(); - labelTextNode.attr('x', isHorizontal ? 0 : (-box.height)); - } -}; - -var d3_decorator_wrap_tick_label = (nodeScale, guide, isHorizontal) => { - - var angle = guide.rotate; - - var ticks = nodeScale.selectAll('.tick text'); - ticks - .attr('transform', utilsDraw.rotate(angle)) - .style('text-anchor', guide.textAnchor); - - if (angle === 90) { - var dy = parseFloat(ticks.attr('dy')) / 2; - ticks.attr('x', 9).attr('y', 0).attr('dy', `${dy}em`); - } - - if (guide.tickFormatWordWrap) { - ticks.call( - wrapText, - guide.tickFormatWordWrapLimit, - guide.tickFormatWordWrapLines, - guide.$maxTickTextH, - !isHorizontal - ); - } else { - ticks - .call(cutText, guide.tickFormatWordWrapLimit); - } -}; +import { + d3_decorator_wrap_tick_label, + d3_decorator_prettify_axis_label, + d3_decorator_fix_axis_bottom_line, + d3_decorator_fix_horizontal_axis_ticks_overflow, + d3_decorator_prettify_categorical_axis_ticks + } from '../utils/d3-decorators'; export class Cartesian { diff --git a/src/utils/d3-decorators.js b/src/utils/d3-decorators.js new file mode 100644 index 000000000..88ab4b17a --- /dev/null +++ b/src/utils/d3-decorators.js @@ -0,0 +1,252 @@ +import {utilsDraw} from '../utils/utils-draw'; +import {default as _} from 'underscore'; +import {default as d3} from 'd3'; + +var d3getComputedTextLength = _.memoize( + (d3Text) => d3Text.node().getComputedTextLength(), + (d3Text) => d3Text.node().textContent.length); + +var cutText = (textString, widthLimit, getComputedTextLength) => { + + getComputedTextLength = getComputedTextLength || d3getComputedTextLength; + + textString.each(function () { + var textD3 = d3.select(this); + var tokens = textD3.text().split(/\s+/); + + var stop = false; + var parts = tokens.reduce((memo, t, i) => { + + if (stop) { + return memo; + } + + var text = (i > 0) ? [memo, t].join(' ') : t; + var len = getComputedTextLength(textD3.text(text)); + if (len < widthLimit) { + memo = text; + } else { + var available = Math.floor(widthLimit / len * text.length); + memo = text.substr(0, available - 4) + '...'; + stop = true; + } + + return memo; + + }, ''); + + textD3.text(parts); + }); +}; + +var wrapText = (textNode, widthLimit, linesLimit, tickLabelFontHeight, isY, getComputedTextLength) => { + + getComputedTextLength = getComputedTextLength || d3getComputedTextLength; + + var addLine = (targetD3, text, lineHeight, x, y, dy, lineNumber) => { + var dyNew = (lineNumber * lineHeight) + dy; + return targetD3 + .append('tspan') + .attr('x', x) + .attr('y', y) + .attr('dy', dyNew + 'em') + .text(text); + }; + + textNode.each(function () { + var textD3 = d3.select(this), + tokens = textD3.text().split(/\s+/), + lineHeight = 1.1, // ems + x = textD3.attr('x'), + y = textD3.attr('y'), + dy = parseFloat(textD3.attr('dy')); + + textD3.text(null); + var tempSpan = addLine(textD3, null, lineHeight, x, y, dy, 0); + + var stopReduce = false; + var tokensCount = (tokens.length - 1); + var lines = tokens + .reduce((memo, next, i) => { + + if (stopReduce) { + return memo; + } + + var isLimit = (memo.length === linesLimit) || (i === tokensCount); + var last = memo[memo.length - 1]; + var text = (last !== '') ? (last + ' ' + next) : next; + var tLen = getComputedTextLength(tempSpan.text(text)); + var over = tLen > widthLimit; + + if (over && isLimit) { + var available = Math.floor(widthLimit / tLen * text.length); + memo[memo.length - 1] = text.substr(0, available - 4) + '...'; + stopReduce = true; + } + + if (over && !isLimit) { + memo.push(next); + } + + if (!over) { + memo[memo.length - 1] = text; + } + + return memo; + + }, ['']) + .filter((l) => l.length > 0); + + y = isY ? (-1 * (lines.length - 1) * Math.floor(tickLabelFontHeight * 0.5)) : y; + lines.forEach((text, i) => addLine(textD3, text, lineHeight, x, y, dy, i)); + + tempSpan.remove(); + }); +}; + +var d3_decorator_prettify_categorical_axis_ticks = (nodeAxis, size, isHorizontal) => { + + var selection = nodeAxis.selectAll('.tick line'); + if (selection.empty()) { + return; + } + + var sectorSize = size / selection[0].length; + var offsetSize = sectorSize / 2; + + var key = (isHorizontal) ? 'x' : 'y'; + var val = (isHorizontal) ? offsetSize : (-offsetSize); + + selection.attr(key + '1', val).attr(key + '2', val); +}; + +var d3_decorator_fix_horizontal_axis_ticks_overflow = (axisNode) => { + + var timeTicks = axisNode.selectAll('.tick')[0]; + if (timeTicks.length < 2) { + return; + } + + var tick0 = parseFloat(timeTicks[0].attributes.transform.value.replace('translate(', '')); + var tick1 = parseFloat(timeTicks[1].attributes.transform.value.replace('translate(', '')); + + var tickStep = tick1 - tick0; + + var maxTextLn = 0; + var iMaxTexts = -1; + var timeTexts = axisNode.selectAll('.tick text')[0]; + timeTexts.forEach((textNode, i) => { + var innerHTML = textNode.textContent || ''; + var textLength = innerHTML.length; + if (textLength > maxTextLn) { + maxTextLn = textLength; + iMaxTexts = i; + } + }); + + if (iMaxTexts >= 0) { + var rect = timeTexts[iMaxTexts].getBoundingClientRect(); + // 2px from each side + if ((tickStep - rect.width) < 8) { + axisNode.classed({'graphical-report__d3-time-overflown': true}); + } + } +}; + +var d3_decorator_fix_axis_bottom_line = (axisNode, size, isContinuesScale) => { + + var selection = axisNode.selectAll('.tick line'); + if (selection.empty()) { + return; + } + + var tickOffset = -1; + + if (isContinuesScale) { + tickOffset = 0; + } else { + var sectorSize = size / selection[0].length; + var offsetSize = sectorSize / 2; + tickOffset = (-offsetSize); + } + + var tickGroupClone = axisNode.select('.tick').node().cloneNode(true); + axisNode + .append(() => tickGroupClone) + .attr('transform', utilsDraw.translate(0, size - tickOffset)); +}; + +var d3_decorator_prettify_axis_label = (axisNode, guide, isHorizontal) => { + + var koeff = (isHorizontal) ? 1 : -1; + var labelTextNode = axisNode + .append('text') + .attr('transform', utilsDraw.rotate(guide.rotate)) + .attr('class', guide.cssClass) + .attr('x', koeff * guide.size * 0.5) + .attr('y', koeff * guide.padding) + .style('text-anchor', guide.textAnchor); + + var delimiter = ' > '; + var tags = guide.text.split(delimiter); + var tLen = tags.length; + tags.forEach((token, i) => { + + labelTextNode + .append('tspan') + .attr('class', 'label-token label-token-' + i) + .text(token); + + if (i < (tLen - 1)) { + labelTextNode + .append('tspan') + .attr('class', 'label-token-delimiter label-token-delimiter-' + i) + .text(delimiter); + } + }); + + if (guide.dock === 'right') { + let box = axisNode.selectAll('path.domain').node().getBBox(); + labelTextNode.attr('x', isHorizontal ? (box.width) : 0); + } else if (guide.dock === 'left') { + let box = axisNode.selectAll('path.domain').node().getBBox(); + labelTextNode.attr('x', isHorizontal ? 0 : (-box.height)); + } +}; + +var d3_decorator_wrap_tick_label = (nodeScale, guide, isHorizontal) => { + + var angle = guide.rotate; + + var ticks = nodeScale.selectAll('.tick text'); + ticks + .attr('transform', utilsDraw.rotate(angle)) + .style('text-anchor', guide.textAnchor); + + if (angle === 90) { + var dy = parseFloat(ticks.attr('dy')) / 2; + ticks.attr('x', 9).attr('y', 0).attr('dy', `${dy}em`); + } + + if (guide.tickFormatWordWrap) { + ticks.call( + wrapText, + guide.tickFormatWordWrapLimit, + guide.tickFormatWordWrapLines, + guide.$maxTickTextH, + !isHorizontal + ); + } else { + ticks + .call(cutText, guide.tickFormatWordWrapLimit); + } +}; + +export { + d3_decorator_wrap_tick_label, + d3_decorator_prettify_axis_label, + d3_decorator_fix_axis_bottom_line, + d3_decorator_fix_horizontal_axis_ticks_overflow, + d3_decorator_prettify_categorical_axis_ticks +}; \ No newline at end of file From 25ca711224a0473c94b933e54e53a94ae75e97a0 Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Tue, 3 Mar 2015 13:12:03 +0300 Subject: [PATCH 36/93] algebra is extracted to separate file --- src/algebra.js | 49 +++++++++++++++++++++++ src/charts/tau.gpl.js | 65 ++++--------------------------- test/algebra.test.js | 90 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+), 57 deletions(-) create mode 100644 src/algebra.js create mode 100644 test/algebra.test.js diff --git a/src/algebra.js b/src/algebra.js new file mode 100644 index 000000000..466a5e39c --- /dev/null +++ b/src/algebra.js @@ -0,0 +1,49 @@ +import {default as _} from 'underscore'; + +var unify = (v) => (v instanceof Date) ? v.getTime() : v; + +var FramesAlgebra = { + + cross(dataFn, dimX, dimY) { + + var data = dataFn(); + + var domainX = _(data).chain().pluck(dimX).unique(unify).value(); + var domainY = _(data).chain().pluck(dimY).unique(unify).value(); + + var domX = domainX.length === 0 ? [null] : domainX; + var domY = domainY.length === 0 ? [null] : domainY; + + return _(domY).reduce( + (memo, rowVal) => { + + return memo.concat(_(domX).map((colVal) => { + + var r = {}; + + if (dimX) { + r[dimX] = unify(colVal); + } + + if (dimY) { + r[dimY] = unify(rowVal); + } + + return r; + })); + }, + []); + }, + + groupBy(dataFn, dim) { + var data = dataFn(); + var domainX = _(data).chain().pluck(dim).unique(unify).value(); + return domainX.map((x)=>({[dim]: unify(x)})); + }, + + none() { + return [null]; + } +}; + +export {FramesAlgebra}; \ No newline at end of file diff --git a/src/charts/tau.gpl.js b/src/charts/tau.gpl.js index bf0c34d5d..d0c7cbcf6 100644 --- a/src/charts/tau.gpl.js +++ b/src/charts/tau.gpl.js @@ -5,50 +5,7 @@ import {UnitsRegistry} from '../units-registry'; import {getLayout} from '../utils/layuot-template'; import {ScalesFactory} from '../scales-factory'; import {CSS_PREFIX} from '../const'; - -var FramesAlgebra = { - - cross: function (dataFn, dimX, dimY) { - - var convert = (v) => (v instanceof Date) ? v.getTime() : v; - - var data = dataFn(); - - var domainX = _(data).chain().pluck(dimX).unique(convert).value(); - var domainY = _(data).chain().pluck(dimY).unique(convert).value(); - - var domX = domainX.length === 0 ? [null] : domainX; - var domY = domainY.length === 0 ? [null] : domainY; - - return _(domY).reduce( - (memo, rowVal) => { - - return memo.concat(_(domX).map((colVal) => { - - var r = {}; - - if (dimX) { - r[dimX] = convert(colVal); - } - - if (dimY) { - r[dimY] = convert(rowVal); - } - - return r; - })); - }, - []); - }, - groupBy(dataFn, dim) { - var data = dataFn(); - var domainX = _(data).chain().pluck(dim).unique().value(); - return domainX.map((x)=>({[dim]:x})); - }, - none: function (datus, dimX, dimY, pipe) { - return [null]; - } -}; +import {FramesAlgebra} from '../algebra'; var calcBaseFrame = (unitExpression, baseFrame) => { @@ -115,7 +72,7 @@ export class GPL extends Emitter { } // expand units structure - this.root = this.expandUnitsStructure(this.config.unit); + this.root = this._expandUnitsStructure(this.config.unit); container.selectAll('svg') .data(['const']) @@ -137,7 +94,7 @@ export class GPL extends Emitter { this._drawUnitsStructure(this.root); } - expandUnitsStructure(root, parentPipe = []) { + _expandUnitsStructure(root, parentPipe = []) { if (root.expression.operator !== false) { @@ -171,15 +128,11 @@ export class GPL extends Emitter { }); } - root.frames.map((item) => { - // key: tuple, - // source: expr.source, - // pipe: pipe - - item.units.map((unit) => this.expandUnitsStructure(unit, item.pipe)); - - return item; - }); + root.frames.forEach( + (f) => (f.units.forEach( + (unit) => this._expandUnitsStructure(unit, f.pipe) + )) + ); return root; } @@ -198,8 +151,6 @@ export class GPL extends Emitter { var name = alias ? alias : `${type}:default`; - name = name.scaleDim || name; - return self.scalesCreator.create(self.scales[name], dataFrame, settings); }) .drawFrames(rootConf.frames.map(self._datify.bind(self)), self._drawUnitsStructure.bind(self)); diff --git a/test/algebra.test.js b/test/algebra.test.js new file mode 100644 index 000000000..fe9c839c6 --- /dev/null +++ b/test/algebra.test.js @@ -0,0 +1,90 @@ +define(function (require) { + var expect = require('chai').expect; + var assert = require('chai').assert; + var algebra = require('tau_modules/algebra').FramesAlgebra; + + describe("operator:cross", function () { + + it("should generate tuples", function () { + + var dataFn = function () { + return [ + {x1: 'x', y1: 'y', z1: 1}, + {x1: 'x', y1: 'y', z1: 2}, + {x1: 'x', y1: 'y', z1: 3} + ]; + }; + + var tuples0 = algebra.cross(dataFn, 'x1', 'y1'); + expect(tuples0).to.deep.equal([ + {x1: 'x', y1: 'y'} + ]); + + var tuples1 = algebra.cross(dataFn, 'x1', 'z1'); + expect(tuples1).to.deep.equal([ + {x1: 'x', z1: 1}, + {x1: 'x', z1: 2}, + {x1: 'x', z1: 3} + ]); + + var tuples2 = algebra.cross(dataFn, null, 'z1'); + expect(tuples2).to.deep.equal([ + {z1: 1}, + {z1: 2}, + {z1: 3} + ]); + + var tuples3 = algebra.cross(dataFn, 'x1', null); + expect(tuples3).to.deep.equal([ + {x1: 'x'} + ]); + }); + + }); + + describe("operator:none", function () { + + it("should generate empty tuple", function () { + + var dataFn = function () { + return [ + {x1: 'x', y1: 'y', z1: 1}, + {x1: 'x', y1: 'y', z1: 2}, + {x1: 'x', y1: 'y', z1: 3} + ]; + }; + + var tuples0 = algebra.none(dataFn, 'x1', 'y1'); + expect(tuples0).to.deep.equal([null]); + }); + + }); + + describe("operator:groupBy", function () { + + it("should generate group tuples", function () { + + var dataFn = function () { + return [ + {x1: 'x', y1: 'y', z1: 1}, + {x1: 'x', y1: 'y', z1: 2}, + {x1: 'x', y1: 'y', z1: 3} + ]; + }; + + var tuples0 = algebra.groupBy(dataFn, 'x1'); + expect(tuples0).to.deep.equal([{x1:'x'}]); + + var tuples1 = algebra.groupBy(dataFn, 'z1'); + expect(tuples1).to.deep.equal([ + {z1:1}, + {z1:2}, + {z1:3} + ]); + + var tuples2 = algebra.groupBy(dataFn, 'some-property'); + expect(tuples2).to.deep.equal([{"some-property":undefined}]); + }); + + }); +}); From 98c484a5a4edbb3db5e44d8db6eb8b0fbb4ff0f0 Mon Sep 17 00:00:00 2001 From: konstantin Date: Tue, 3 Mar 2015 14:58:10 +0300 Subject: [PATCH 37/93] update interval test for new api --- Gruntfile.js | 2 +- examples/streaming.html | 7 + src/elements/element.interval.fn.js | 40 ++- src/elements/element.interval.js | 8 +- src/elements/interval.js | 87 ------ src/node-map.js | 6 - src/tau.charts.js | 6 +- test/element-interval.test.js | 453 ++++++++++++++++------------ test/utils/schemes.js | 28 +- 9 files changed, 339 insertions(+), 298 deletions(-) delete mode 100644 src/elements/interval.js diff --git a/Gruntfile.js b/Gruntfile.js index ad74bdc08..59625944b 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -280,7 +280,7 @@ module.exports = function (grunt) { port: 9000, keepAlive: true, webpack: { - devtool: 'sourcemap', + devtool: 'eval', debug: true } } diff --git a/examples/streaming.html b/examples/streaming.html index e455a07ea..d24eb3c8c 100644 --- a/examples/streaming.html +++ b/examples/streaming.html @@ -150,6 +150,13 @@ bug: 'ISSUE4', count: 10, date: new Date('2014-01-05') + },{ + project: 'TP3', + team: 'xbeta', + story: 'B1', + bug: 'ISSUE4', + count: 10, + date: new Date('2014-01-05') }, { project: 'TP3', diff --git a/src/elements/element.interval.fn.js b/src/elements/element.interval.fn.js index 5540ddc55..573f67be2 100644 --- a/src/elements/element.interval.fn.js +++ b/src/elements/element.interval.fn.js @@ -1,4 +1,6 @@ import {utilsDraw} from '../utils/utils-draw'; +import {CSS_PREFIX} from '../const'; +const BAR_GROUP = 'i-role-bar-group'; var getSizesParams = (params) => { var countDomainValue = params.domain().length; var countCategory = params.categoryLength; @@ -96,4 +98,40 @@ var flipHub = { } }; -export {flipHub}; \ No newline at end of file +function drawInterval({ + calculateX, + calculateY, + colorScale, + calculateWidth, + calculateHeight, + calculateTranslate + }, + container, + data) { + var updateBar = function () { + return this + .attr('height', calculateHeight) + .attr('width', calculateWidth) + .attr('class', (d) => { + return `i-role-element i-role-datum bar ${CSS_PREFIX}bar ${colorScale(d[colorScale.scaleDim])}`; + }) + .attr('x', calculateX) + .attr('y', calculateY); + }; + + var updateBarContainer = function () { + this.attr('class', BAR_GROUP) + .attr('transform', calculateTranslate); + var bars = this.selectAll('bar').data((d) => d.values); + bars.call(updateBar); + bars.enter().append('rect').call(updateBar); + bars.exit().remove(); + }; + + var elements = container.selectAll(`.${BAR_GROUP}`).data(data); + elements.call(updateBarContainer); + elements.enter().append('g').call(updateBarContainer); + elements.exit().remove(); +} + +export {flipHub, drawInterval}; \ No newline at end of file diff --git a/src/elements/element.interval.js b/src/elements/element.interval.js index 2680d338c..78107fbe1 100644 --- a/src/elements/element.interval.js +++ b/src/elements/element.interval.js @@ -1,6 +1,5 @@ import {CSS_PREFIX} from '../const'; -import {flipHub} from './element.interval.fn'; -import {interval, drawInterval} from './interval'; +import {flipHub, drawInterval} from './element.interval.fn'; export class Interval { @@ -43,13 +42,14 @@ export class Interval { }; var method = flipHub[node.flip ? 'FLIP' : 'NORM']; var colorIndexScale = (d) => { - return _.findIndex(domain, (value)=> { + var findIndex = _.findIndex(domain, (value)=> { return value === d.key[colorScale.scaleDim]; }); + return findIndex === -1 ? 0 : findIndex; }; // colorScale.scaleDim = node.color.scaleDim; var domain = colorScale.domain(); - colorIndexScale.count = () => domain.length; + colorIndexScale.count = () => domain.length || 1; var params = method({ node, diff --git a/src/elements/interval.js b/src/elements/interval.js deleted file mode 100644 index 37c7d0c4d..000000000 --- a/src/elements/interval.js +++ /dev/null @@ -1,87 +0,0 @@ -import {CSS_PREFIX} from '../const'; -import {flipHub} from './element.interval.fn'; - -const BAR_GROUP = 'i-role-bar-group'; - -var interval = function (node) { - - var options = node.options; - - var xScale = options.xScale, - yScale = options.yScale, - colorScale = options.color; - - var method = flipHub[node.flip ? 'FLIP' : 'NORM']; - - var allCategories = node.groupBy(node.source(), node.color.scaleDim); - var categories = node.groupBy(node.partition(), node.color.scaleDim); - - var colorIndexScale = (d) => { - var index = 0; - var targetKey = JSON.stringify(d.key); - _.find(allCategories, (catItem, catIndex) => { - var isFound = (JSON.stringify(catItem.key) === targetKey); - if (isFound) { - index = catIndex; - } - return isFound; - }); - - return index; - }; - colorScale.scaleDim = node.color.scaleDim; - colorIndexScale.count = () => allCategories.length; - - var params = method({ - node, - xScale, - yScale, - colorScale, - colorIndexScale, - width: options.width, - height: options.height, - defaultSizeParams: { - tickWidth: 5, - intervalWidth: 5, - offsetCategory: 0 - } - }); - drawInterval(params, options.container, categories); -}; -function drawInterval({ - calculateX, - calculateY, - colorScale, - calculateWidth, - calculateHeight, - calculateTranslate - }, - container, - data) { - var updateBar = function () { - return this - .attr('height', calculateHeight) - .attr('width', calculateWidth) - .attr('class', (d) => { - return `i-role-element i-role-datum bar ${CSS_PREFIX}bar ${colorScale(d[colorScale.scaleDim])}`; - }) - .attr('x', calculateX) - .attr('y', calculateY); - }; - - var updateBarContainer = function () { - this.attr('class', BAR_GROUP) - .attr('transform', calculateTranslate); - var bars = this.selectAll('bar').data((d) => d.values); - bars.call(updateBar); - bars.enter().append('rect').call(updateBar); - bars.exit().remove(); - }; - - var elements = container.selectAll(`.${BAR_GROUP}`).data(data); - elements.call(updateBarContainer); - elements.enter().append('g').call(updateBarContainer); - elements.exit().remove(); -} - -export {interval, drawInterval}; \ No newline at end of file diff --git a/src/node-map.js b/src/node-map.js index aa86d462d..c352be619 100644 --- a/src/node-map.js +++ b/src/node-map.js @@ -1,7 +1,6 @@ import {coords} from './elements/coords'; import {line} from './elements/line'; import {point} from './elements/point'; -import {interval} from './elements/interval'; import {utilsDraw} from './utils/utils-draw'; import {CoordsParallel} from './elements/coords-parallel'; import {CoordsParallelLine} from './elements/coords-parallel-line'; @@ -70,11 +69,6 @@ var nodeMap = { 'ELEMENT.LINE': (node) => { return line(setupElementNode(node, ['x', 'y', 'color'])); }, - - 'ELEMENT.INTERVAL': function (node) { - return interval(setupElementNode(node, ['x', 'y', 'color'])); - }, - 'COORDS.PARALLEL': CoordsParallel, 'PARALLEL/ELEMENT.LINE': CoordsParallelLine }; diff --git a/src/tau.charts.js b/src/tau.charts.js index 1323b8eca..59f20a034 100644 --- a/src/tau.charts.js +++ b/src/tau.charts.js @@ -119,14 +119,12 @@ Plot.globalSettings = api.globalSettings; api.UnitsRegistry .add('COORDS.PARALLEL', nodeMap['COORDS.PARALLEL']) .add('PARALLEL/ELEMENT.LINE', nodeMap['PARALLEL/ELEMENT.LINE']) - .add('COORDS.RECT', nodeMap['COORDS.RECT']) .add('ELEMENT.POINT', nodeMap['ELEMENT.POINT']) .add('ELEMENT.LINE', nodeMap['ELEMENT.LINE']) - .add('ELEMENT.INTERVAL', nodeMap['ELEMENT.INTERVAL']) - .reg('RECT', Cartesian) + .reg('COORDS.RECT', Cartesian) .reg('POINT', Point) - .reg('INTERVAL', Interval) + .reg('ELEMENT.INTERVAL', Interval) .reg('LINE', Line) .reg('PIE', Pie); diff --git a/test/element-interval.test.js b/test/element-interval.test.js index db4bd0b36..a90219dad 100644 --- a/test/element-interval.test.js +++ b/test/element-interval.test.js @@ -1,8 +1,10 @@ +// jscs:disable disallowQuotedKeysInObjects +// jscs:disable validateQuoteMarks define(function (require) { var expect = require('chai').expect; var schemes = require('schemes'); var assert = require('chai').assert; - var tauChart = require('tau_modules/tau.charts').tauChart; + var tauCharts = require('tau_modules/tau.charts'); var testUtils = require('testUtils'); var getGroupBar = testUtils.getGroupBar; var attrib = testUtils.attrib; @@ -24,8 +26,95 @@ define(function (require) { }); return coords[0]; }*/ + var convertSpec = function (spec, data) { + var unit = spec.unit; + return { + sources: { + '?': { + dims: {}, + data: [] + }, + '/': { + dims: { + x: {type: 'category'}, + y: {type: 'measure'}, + color: {type: 'category'} + }, + data: data + } + }, + trans: { + where: function (data, tuple) { + var predicates = _.map(tuple, function (v, k) { + return function (row) { + return (row[k] === v); + } + }); + return _(data).filter(function (row) { + return _.every(predicates, function (p) { + return p(row); + }) + }); + } + }, + scales: { + 'x': {type: 'ordinal', source: '/', dim: 'x'}, + 'y': {type: 'linear', source: '/', dim: 'y'}, + 'size:default': {type: 'size', source: '?', mid: 5}, + 'color': {type: 'color', dim: 'color', source: '/'}, + 'color:default': {type: 'color', source: '?', brewer: null} + }, + unit: { + type: 'COORDS.RECT', + expression: { + inherit: false, + source: '/', + operator: 'none' + }, + x: unit.x, + y: unit.y, + units: [{ + type: 'ELEMENT.INTERVAL', + x: unit.unit[0].x || unit.x, + y: unit.unit[0].y || unit.y, + color: 'color', + expression: { + inherit: true, + source: '/', + operator: 'groupBy', + params: ['color'] + } + }] + } + } + }; + var describePlot = /*testUtils.describePlot;*/ + + function d(name, spec, data, fn) { + describe(name, function () { + var context = { + element: null, + chart: null + }; + + beforeEach(function () { + context.element = document.createElement('div'); + document.body.appendChild(context.element); + + // tauCharts.Plot.globalSettings = testChartSettings; + + context.chart = new tauCharts.GPL(convertSpec(spec, data)); + + context.chart.renderTo(context.element, {width: 800, height: 800}); + }); + + fn(context); - var describePlot = testUtils.describePlot; + afterEach(function () { + context.element.parentNode.removeChild(context.element); + }); + }); + }; // testUtils.describePlot; var describeChart = testUtils.describeChart; var expectCoordsElement = function (expect, coords) { var bars = getGroupBar(); @@ -38,7 +127,7 @@ define(function (require) { _.each(bar.childNodes, function (el, ind) { expect(convertToFixed(attrib(el, 'x'))).to.equal(convertToFixed(coords[index][ind].x)); expect(convertToFixed(attrib(el, 'y'))).to.equal(convertToFixed(coords[index][ind].y)); - if(coords[index][ind].width) { + if (coords[index][ind].width) { expect(convertToFixed(attrib(el, 'width'))).to.equal(convertToFixed(coords[index][ind].width)); } }); @@ -77,43 +166,39 @@ define(function (require) { function (context) { it("should render group bar element", function () { var chart = context.chart; - assert.ok(schemes.bar(chart.config.spec), 'spec is right'); + assert.ok(!schemes.bar.errors(chart.config), 'spec is right'); expect(getGroupBar().length).to.equal(3); }); it("should group contain interval element", function () { + // debugger expectCoordsElement(expect, [ - //generate with help generateCoordIfChangeDesign [ { "x": "0", - "y": "457", - "width":"66.66666666666667" + "y": "429" } ], [ { - "x": "266.66666666666663", - "y": "514", - "width":"66.66666666666667" + "x": "250", + "y": "482" }, { - "x": "533.3333333333334", - "y": "0", - "width":"66.66666666666667" + "x": "500", + "y": "0" } ], [ { - "x": "533.3333333333334", - "y": "571", - "width":"66.66666666666667" + "x": "500", + "y": "536" } ] ]); }); } ); - describePlot( + /* describePlot( "ELEMENT.INTERVAL WITH TWO LINEAR AXIS", { unit: { @@ -135,7 +220,7 @@ define(function (require) { function () { it("should group contain interval element", function () { expectCoordsElement(expect, [ - //generate with help generateCoordIfChangeDesign + // generate with help generateCoordIfChangeDesign [ { @@ -183,7 +268,7 @@ define(function (require) { function () { it("should group contain interval element", function () { expectCoordsElement(expect, [ - //generate with help generateCoordIfChangeDesign + // generate with help generateCoordIfChangeDesign [ { @@ -239,7 +324,7 @@ define(function (require) { }); it("should group contain interval element", function () { expectCoordsElement(expect, [ - //generate with help generateCoordIfChangeDesign + // generate with help generateCoordIfChangeDesign [ { "x": "229", @@ -290,7 +375,7 @@ define(function (require) { function () { it("should group contain interval element", function () { expectCoordsElement(expect, [ - //generate with help generateCoordIfChangeDesign + // generate with help generateCoordIfChangeDesign [ { @@ -339,7 +424,7 @@ define(function (require) { function () { it("should group contain interval element", function () { expectCoordsElement(expect, [ - //generate with help generateCoordIfChangeDesign + // generate with help generateCoordIfChangeDesign [ { @@ -417,29 +502,27 @@ define(function (require) { }, dataWithDate, function () { - it("should group contain interval element", function () { - expectCoordsElement(expect, [ - //generate with help generateCoordIfChangeDesign - - - [ - { - "x": "0", - "y": "43" - }, - { - "x": "514.2857", - "y": "591" - }, - { - "x": "780.9524", - "y": "788" - } - ] - - - ]); - }); + /!* it("should group contain interval element", function () { + expectCoordsElement(expect, [ + // generate with help generateCoordIfChangeDesign + + [ + { + "x": "0", + "y": "43" + }, + { + "x": "514.2857", + "y": "591" + }, + { + "x": "780.9524", + "y": "788" + } + ] + + ]); + });*!/ } ); @@ -477,25 +560,25 @@ define(function (require) { }, dataWithDate, function () { - it("should group contain interval element", function () { - expectCoordsElement(expect, [ - //generate with help generateCoordIfChangeDesign - [ - { - "x": "0", - "y": "780.9524" - }, - { - "x": "0", - "y": "266.6667" - }, - { - "x": "0", - "y": "-0.000000001" - } - ] - ]); - }); + /!* it("should group contain interval element", function () { + expectCoordsElement(expect, [ + // generate with help generateCoordIfChangeDesign + [ + { + "x": "0", + "y": "780.9524" + }, + { + "x": "0", + "y": "266.6667" + }, + { + "x": "0", + "y": "-0.000000001" + } + ] + ]); + });*!/ } ); @@ -532,26 +615,26 @@ define(function (require) { {time: testUtils.toLocalDate('2014-02-01'), count: 10} ], function () { - it("should group contain interval element", function () { - - var minimalHeight = 1; - - var coords = [ - [ - 800, - 400, - minimalHeight - ] - ]; - - var bars = getGroupBar(); - - _.each(bars, function (bar, barIndex) { - _.each(bar.childNodes, function (el, elIndex) { - expect(parseFloat(attrib(el, 'height'))).to.equal(coords[barIndex][elIndex]); - }); - }); - }); + /!* it("should group contain interval element", function () { + + var minimalHeight = 1; + + var coords = [ + [ + 800, + 400, + minimalHeight + ] + ]; + + var bars = getGroupBar(); + + _.each(bars, function (bar, barIndex) { + _.each(bar.childNodes, function (el, elIndex) { + expect(parseFloat(attrib(el, 'height'))).to.equal(coords[barIndex][elIndex]); + }); + }); + });*!/ }); describePlot( @@ -588,26 +671,26 @@ define(function (require) { {time: testUtils.toLocalDate('2014-02-01'), count: 10} ], function () { - it("should group contain interval element", function () { - - var minimalHeight = 1; - - var coords = [ - [ - 800, - 400, - minimalHeight - ] - ]; - - var bars = getGroupBar(); - - _.each(bars, function (bar, barIndex) { - _.each(bar.childNodes, function (el, elIndex) { - expect(parseFloat(attrib(el, 'width'))).to.equal(coords[barIndex][elIndex]); - }); - }); - }); + /!* it("should group contain interval element", function () { + + var minimalHeight = 1; + + var coords = [ + [ + 800, + 400, + minimalHeight + ] + ]; + + var bars = getGroupBar(); + + _.each(bars, function (bar, barIndex) { + _.each(bar.childNodes, function (el, elIndex) { + expect(parseFloat(attrib(el, 'width'))).to.equal(coords[barIndex][elIndex]); + }); + }); + });*!/ }); describePlot( @@ -647,43 +730,43 @@ define(function (require) { {time: testUtils.toLocalDate('2014-02-07'), count: -1000} ], function () { - it("should group contain interval element", function () { - - var minimalHeight = 1; - - var coords = [ - [ - 400, - 200, - minimalHeight, - 0, - minimalHeight, - 200, - 400 - ] - ]; - - var ys = [ - [ - 0, // count = 1000 - 200, // count = 500 - 399, // count = 1 (minus minimal height) - 400, // count = 0 - 400, // count = -1 - 400, // count = -500 - 400 // count = -1000 - ] - ]; - - var bars = getGroupBar(); - - _.each(bars, function (bar, barIndex) { - _.each(bar.childNodes, function (el, elIndex) { - expect(parseFloat(attrib(el, 'y'))).to.equal(ys[barIndex][elIndex]); - expect(parseFloat(attrib(el, 'height'))).to.equal(coords[barIndex][elIndex]); - }); - }); - }); + /!*it("should group contain interval element", function () { + + var minimalHeight = 1; + + var coords = [ + [ + 400, + 200, + minimalHeight, + 0, + minimalHeight, + 200, + 400 + ] + ]; + + var ys = [ + [ + 0, // count = 1000 + 200, // count = 500 + 399, // count = 1 (minus minimal height) + 400, // count = 0 + 400, // count = -1 + 400, // count = -500 + 400 // count = -1000 + ] + ]; + + var bars = getGroupBar(); + + _.each(bars, function (bar, barIndex) { + _.each(bar.childNodes, function (el, elIndex) { + expect(parseFloat(attrib(el, 'y'))).to.equal(ys[barIndex][elIndex]); + expect(parseFloat(attrib(el, 'height'))).to.equal(coords[barIndex][elIndex]); + }); + }); + });*!/ }); describePlot( @@ -724,43 +807,43 @@ define(function (require) { {time: testUtils.toLocalDate('2014-02-07'), count: -1000} ], function () { - it("should group contain interval element", function () { - - var minimalHeight = 1; - - var coords = [ - [ - 400, - 200, - minimalHeight, - 0, - minimalHeight, - 200, - 400 - ] - ]; - - var xs = [ - [ - 400, // count = 1000 - 400, // count = 500 - 400, // count = 1 - 400, // count = 0 - 399, // count = -1 (minus minimal height) - 200, // count = -500 - 0 // count = -1000 - ] - ]; - - var bars = getGroupBar(); - - _.each(bars, function (bar, barIndex) { - _.each(bar.childNodes, function (el, elIndex) { - expect(parseFloat(attrib(el, 'x'))).to.equal(xs[barIndex][elIndex]); - expect(parseFloat(attrib(el, 'width'))).to.equal(coords[barIndex][elIndex]); - }); - }); - }); + /!* it("should group contain interval element", function () { + + var minimalHeight = 1; + + var coords = [ + [ + 400, + 200, + minimalHeight, + 0, + minimalHeight, + 200, + 400 + ] + ]; + + var xs = [ + [ + 400, // count = 1000 + 400, // count = 500 + 400, // count = 1 + 400, // count = 0 + 399, // count = -1 (minus minimal height) + 200, // count = -500 + 0 // count = -1000 + ] + ]; + + var bars = getGroupBar(); + + _.each(bars, function (bar, barIndex) { + _.each(bar.childNodes, function (el, elIndex) { + expect(parseFloat(attrib(el, 'x'))).to.equal(xs[barIndex][elIndex]); + expect(parseFloat(attrib(el, 'width'))).to.equal(coords[barIndex][elIndex]); + }); + }); + });*!/ }); describeChart("interval width for facet", @@ -820,17 +903,17 @@ define(function (require) { y: "5" }], function (context) { - it('test position',function(){ - var svg = context.chart.getSVG(); - var offsets = _.map(svg.querySelectorAll('.i-role-bar-group'), function (item) { - return item.getAttribute('transform'); - }); - expect(offsets).to.eql(["translate(66.66666666666667,0)"]); + it('test position', function () { + var svg = context.chart.getSVG(); + var offsets = _.map(svg.querySelectorAll('.i-role-bar-group'), function (item) { + return item.getAttribute('transform'); }); + expect(offsets).to.eql(["translate(66.66666666666667,0)"]); + }); }, { autoWidth: false } - ); + );*/ }); \ No newline at end of file diff --git a/test/utils/schemes.js b/test/utils/schemes.js index bf1e9f5ed..227bda977 100644 --- a/test/utils/schemes.js +++ b/test/utils/schemes.js @@ -5,22 +5,22 @@ define(['js-schema'], function (schema) { var point = schema({ color: [null, String], size: [null, String], - type: "ELEMENT.POINT", + type: 'ELEMENT.POINT', x: [String], y: [String] }); var line = schema({ color: [null, String], - type: "ELEMENT.LINE", + type: 'ELEMENT.LINE', x: [String], y: [String] }); var interval = schema({ color: [null, String], - flip: [Boolean], - type: "ELEMENT.INTERVAL", + flip: [null, Boolean], + type: 'ELEMENT.INTERVAL', x: [String], y: [String] }); @@ -34,26 +34,35 @@ define(['js-schema'], function (schema) { '*': [null, dimension] }; + var scale = schema({ + type: String, + scale: String + }); + + var scales = { + '*': [null, scale] + }; + var scatterplot = schema({ dimensions: dimensions, unit: schema({ guide: undefined, x: [null, String], y: [null, String], - type: "COORDS.RECT", + type: 'COORDS.RECT', unit: Array.of(point) }) }); var bar = schema({ - dimensions: dimensions, + scales: scales, unit: schema({ guide: undefined, x: [null, String], y: [null, String], - type: "COORDS.RECT", - unit: Array.of(interval) + type: 'COORDS.RECT', + units: Array.of(interval) }) }); @@ -64,7 +73,7 @@ define(['js-schema'], function (schema) { guide: undefined, x: [null, String], y: [null, String], - type: "COORDS.RECT", + type: 'COORDS.RECT', unit: Array.of(line) }) @@ -84,4 +93,3 @@ define(['js-schema'], function (schema) { return schemes; }); - From 5ceb03d041de8a052a55484628a9d3b716d7aa0f Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Tue, 3 Mar 2015 15:13:19 +0300 Subject: [PATCH 38/93] remove unused code --- test/algebra.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/algebra.test.js b/test/algebra.test.js index fe9c839c6..042624895 100644 --- a/test/algebra.test.js +++ b/test/algebra.test.js @@ -1,6 +1,5 @@ define(function (require) { var expect = require('chai').expect; - var assert = require('chai').assert; var algebra = require('tau_modules/algebra').FramesAlgebra; describe("operator:cross", function () { From b6d3bb7f8d7a9ac8b3f78e70d2c03e1912a8bb2a Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Tue, 3 Mar 2015 16:14:51 +0300 Subject: [PATCH 39/93] Tests stabilization --- src/elements/interval.js | 87 ++++++ src/node-map.js | 6 + src/tau.charts.js | 6 +- test/element-interval-gpl.test.js | 185 ++++++++++++ test/element-interval.test.js | 453 ++++++++++++------------------ test/utils/schemes.js | 40 ++- 6 files changed, 495 insertions(+), 282 deletions(-) create mode 100644 src/elements/interval.js create mode 100644 test/element-interval-gpl.test.js diff --git a/src/elements/interval.js b/src/elements/interval.js new file mode 100644 index 000000000..37c7d0c4d --- /dev/null +++ b/src/elements/interval.js @@ -0,0 +1,87 @@ +import {CSS_PREFIX} from '../const'; +import {flipHub} from './element.interval.fn'; + +const BAR_GROUP = 'i-role-bar-group'; + +var interval = function (node) { + + var options = node.options; + + var xScale = options.xScale, + yScale = options.yScale, + colorScale = options.color; + + var method = flipHub[node.flip ? 'FLIP' : 'NORM']; + + var allCategories = node.groupBy(node.source(), node.color.scaleDim); + var categories = node.groupBy(node.partition(), node.color.scaleDim); + + var colorIndexScale = (d) => { + var index = 0; + var targetKey = JSON.stringify(d.key); + _.find(allCategories, (catItem, catIndex) => { + var isFound = (JSON.stringify(catItem.key) === targetKey); + if (isFound) { + index = catIndex; + } + return isFound; + }); + + return index; + }; + colorScale.scaleDim = node.color.scaleDim; + colorIndexScale.count = () => allCategories.length; + + var params = method({ + node, + xScale, + yScale, + colorScale, + colorIndexScale, + width: options.width, + height: options.height, + defaultSizeParams: { + tickWidth: 5, + intervalWidth: 5, + offsetCategory: 0 + } + }); + drawInterval(params, options.container, categories); +}; +function drawInterval({ + calculateX, + calculateY, + colorScale, + calculateWidth, + calculateHeight, + calculateTranslate + }, + container, + data) { + var updateBar = function () { + return this + .attr('height', calculateHeight) + .attr('width', calculateWidth) + .attr('class', (d) => { + return `i-role-element i-role-datum bar ${CSS_PREFIX}bar ${colorScale(d[colorScale.scaleDim])}`; + }) + .attr('x', calculateX) + .attr('y', calculateY); + }; + + var updateBarContainer = function () { + this.attr('class', BAR_GROUP) + .attr('transform', calculateTranslate); + var bars = this.selectAll('bar').data((d) => d.values); + bars.call(updateBar); + bars.enter().append('rect').call(updateBar); + bars.exit().remove(); + }; + + var elements = container.selectAll(`.${BAR_GROUP}`).data(data); + elements.call(updateBarContainer); + elements.enter().append('g').call(updateBarContainer); + elements.exit().remove(); +} + +export {interval, drawInterval}; \ No newline at end of file diff --git a/src/node-map.js b/src/node-map.js index c352be619..aa86d462d 100644 --- a/src/node-map.js +++ b/src/node-map.js @@ -1,6 +1,7 @@ import {coords} from './elements/coords'; import {line} from './elements/line'; import {point} from './elements/point'; +import {interval} from './elements/interval'; import {utilsDraw} from './utils/utils-draw'; import {CoordsParallel} from './elements/coords-parallel'; import {CoordsParallelLine} from './elements/coords-parallel-line'; @@ -69,6 +70,11 @@ var nodeMap = { 'ELEMENT.LINE': (node) => { return line(setupElementNode(node, ['x', 'y', 'color'])); }, + + 'ELEMENT.INTERVAL': function (node) { + return interval(setupElementNode(node, ['x', 'y', 'color'])); + }, + 'COORDS.PARALLEL': CoordsParallel, 'PARALLEL/ELEMENT.LINE': CoordsParallelLine }; diff --git a/src/tau.charts.js b/src/tau.charts.js index 59f20a034..1323b8eca 100644 --- a/src/tau.charts.js +++ b/src/tau.charts.js @@ -119,12 +119,14 @@ Plot.globalSettings = api.globalSettings; api.UnitsRegistry .add('COORDS.PARALLEL', nodeMap['COORDS.PARALLEL']) .add('PARALLEL/ELEMENT.LINE', nodeMap['PARALLEL/ELEMENT.LINE']) + .add('COORDS.RECT', nodeMap['COORDS.RECT']) .add('ELEMENT.POINT', nodeMap['ELEMENT.POINT']) .add('ELEMENT.LINE', nodeMap['ELEMENT.LINE']) + .add('ELEMENT.INTERVAL', nodeMap['ELEMENT.INTERVAL']) - .reg('COORDS.RECT', Cartesian) + .reg('RECT', Cartesian) .reg('POINT', Point) - .reg('ELEMENT.INTERVAL', Interval) + .reg('INTERVAL', Interval) .reg('LINE', Line) .reg('PIE', Pie); diff --git a/test/element-interval-gpl.test.js b/test/element-interval-gpl.test.js new file mode 100644 index 000000000..b4f5da732 --- /dev/null +++ b/test/element-interval-gpl.test.js @@ -0,0 +1,185 @@ +// jscs:disable disallowQuotedKeysInObjects +// jscs:disable validateQuoteMarks +define(function (require) { + var expect = require('chai').expect; + var schemes = require('schemes'); + var assert = require('chai').assert; + var tauCharts = require('tau_modules/tau.charts'); + var testUtils = require('testUtils'); + var getGroupBar = testUtils.getGroupBar; + var attrib = testUtils.attrib; + var _ = require('underscore'); + + var convertSpec = function (spec, data) { + var unit = spec.unit; + return { + sources: { + '?': { + dims: {}, + data: [] + }, + '/': { + dims: { + x: {type: 'category'}, + y: {type: 'measure'}, + color: {type: 'category'} + }, + data: data + } + }, + trans: { + where: function (data, tuple) { + var predicates = _.map(tuple, function (v, k) { + return function (row) { + return (row[k] === v); + } + }); + return _(data).filter(function (row) { + return _.every(predicates, function (p) { + return p(row); + }) + }); + } + }, + scales: { + 'x': {type: 'ordinal', source: '/', dim: 'x'}, + 'y': {type: 'linear', source: '/', dim: 'y'}, + 'size:default': {type: 'size', source: '?', mid: 5}, + 'color': {type: 'color', dim: 'color', source: '/'}, + 'color:default': {type: 'color', source: '?', brewer: null} + }, + unit: { + type: 'RECT', + expression: { + inherit: false, + source: '/', + operator: 'none' + }, + x: unit.x, + y: unit.y, + units: [{ + type: 'INTERVAL', + x: unit.unit[0].x || unit.x, + y: unit.unit[0].y || unit.y, + color: 'color', + expression: { + inherit: true, + source: '/', + operator: 'groupBy', + params: ['color'] + } + }] + } + } + }; + + var describePlot = function d(name, spec, data, fn) { + + describe(name, function () { + var context = { + element: null, + chart: null + }; + + beforeEach(function () { + context.element = document.createElement('div'); + document.body.appendChild(context.element); + + // tauCharts.Plot.globalSettings = testChartSettings; + + context.chart = new tauCharts.GPL(convertSpec(spec, data)); + + context.chart.renderTo(context.element, {width: 800, height: 800}); + }); + + fn(context); + + afterEach(function () { + context.element.parentNode.removeChild(context.element); + }); + }); + }; + + var expectCoordsElement = function (expect, coords) { + var bars = getGroupBar(); + + var convertToFixed = function (x) { + return parseFloat(x).toFixed(4); + }; + + _.each(bars, function (bar, index) { + _.each(bar.childNodes, function (el, ind) { + expect(convertToFixed(attrib(el, 'x'))).to.equal(convertToFixed(coords[index][ind].x)); + expect(convertToFixed(attrib(el, 'y'))).to.equal(convertToFixed(coords[index][ind].y)); + if (coords[index][ind].width) { + expect(convertToFixed(attrib(el, 'width'))).to.equal(convertToFixed(coords[index][ind].width)); + } + }); + }); + }; + + describePlot( + "ELEMENT.INTERVAL WITH LINEAR AND CATEGORICAL AXIS", + { + unit: { + type: 'COORDS.RECT', + x: 'x', + y: 'y', + guide: { + x: {autoScale: false}, + y: {autoScale: false} + }, + unit: [ + { + type: 'ELEMENT.INTERVAL', + x: 'x', + flip: false, + y: 'y', + color: 'color' + } + ] + } + }, + [ + {x: 'a', y: 1, color: 'red', size: 6}, + {x: 'b', y: 0.5, color: 'green', size: 6}, + {x: 'c', y: 5, color: 'green', size: 8}, + {x: 'c', y: -2, color: 'yellow', size: 8} + ], + function (context) { + + it("should render group bar element", function () { + var chart = context.chart; + assert.ok(!schemes.barGPL.errors(chart.config), 'spec is right'); + expect(getGroupBar().length).to.equal(3); + }); + + it("should group contain interval element", function () { + expectCoordsElement(expect, [ + [ + { + "x": "0", + "y": "429" + } + ], + [ + { + "x": "250", + "y": "482" + }, + { + "x": "500", + "y": "0" + } + ], + [ + { + "x": "500", + "y": "536" + } + ] + ]); + }); + } + ); +}); \ No newline at end of file diff --git a/test/element-interval.test.js b/test/element-interval.test.js index a90219dad..db4bd0b36 100644 --- a/test/element-interval.test.js +++ b/test/element-interval.test.js @@ -1,10 +1,8 @@ -// jscs:disable disallowQuotedKeysInObjects -// jscs:disable validateQuoteMarks define(function (require) { var expect = require('chai').expect; var schemes = require('schemes'); var assert = require('chai').assert; - var tauCharts = require('tau_modules/tau.charts'); + var tauChart = require('tau_modules/tau.charts').tauChart; var testUtils = require('testUtils'); var getGroupBar = testUtils.getGroupBar; var attrib = testUtils.attrib; @@ -26,95 +24,8 @@ define(function (require) { }); return coords[0]; }*/ - var convertSpec = function (spec, data) { - var unit = spec.unit; - return { - sources: { - '?': { - dims: {}, - data: [] - }, - '/': { - dims: { - x: {type: 'category'}, - y: {type: 'measure'}, - color: {type: 'category'} - }, - data: data - } - }, - trans: { - where: function (data, tuple) { - var predicates = _.map(tuple, function (v, k) { - return function (row) { - return (row[k] === v); - } - }); - return _(data).filter(function (row) { - return _.every(predicates, function (p) { - return p(row); - }) - }); - } - }, - scales: { - 'x': {type: 'ordinal', source: '/', dim: 'x'}, - 'y': {type: 'linear', source: '/', dim: 'y'}, - 'size:default': {type: 'size', source: '?', mid: 5}, - 'color': {type: 'color', dim: 'color', source: '/'}, - 'color:default': {type: 'color', source: '?', brewer: null} - }, - unit: { - type: 'COORDS.RECT', - expression: { - inherit: false, - source: '/', - operator: 'none' - }, - x: unit.x, - y: unit.y, - units: [{ - type: 'ELEMENT.INTERVAL', - x: unit.unit[0].x || unit.x, - y: unit.unit[0].y || unit.y, - color: 'color', - expression: { - inherit: true, - source: '/', - operator: 'groupBy', - params: ['color'] - } - }] - } - } - }; - var describePlot = /*testUtils.describePlot;*/ - - function d(name, spec, data, fn) { - describe(name, function () { - var context = { - element: null, - chart: null - }; - - beforeEach(function () { - context.element = document.createElement('div'); - document.body.appendChild(context.element); - - // tauCharts.Plot.globalSettings = testChartSettings; - - context.chart = new tauCharts.GPL(convertSpec(spec, data)); - - context.chart.renderTo(context.element, {width: 800, height: 800}); - }); - - fn(context); - afterEach(function () { - context.element.parentNode.removeChild(context.element); - }); - }); - }; // testUtils.describePlot; + var describePlot = testUtils.describePlot; var describeChart = testUtils.describeChart; var expectCoordsElement = function (expect, coords) { var bars = getGroupBar(); @@ -127,7 +38,7 @@ define(function (require) { _.each(bar.childNodes, function (el, ind) { expect(convertToFixed(attrib(el, 'x'))).to.equal(convertToFixed(coords[index][ind].x)); expect(convertToFixed(attrib(el, 'y'))).to.equal(convertToFixed(coords[index][ind].y)); - if (coords[index][ind].width) { + if(coords[index][ind].width) { expect(convertToFixed(attrib(el, 'width'))).to.equal(convertToFixed(coords[index][ind].width)); } }); @@ -166,39 +77,43 @@ define(function (require) { function (context) { it("should render group bar element", function () { var chart = context.chart; - assert.ok(!schemes.bar.errors(chart.config), 'spec is right'); + assert.ok(schemes.bar(chart.config.spec), 'spec is right'); expect(getGroupBar().length).to.equal(3); }); it("should group contain interval element", function () { - // debugger expectCoordsElement(expect, [ + //generate with help generateCoordIfChangeDesign [ { "x": "0", - "y": "429" + "y": "457", + "width":"66.66666666666667" } ], [ { - "x": "250", - "y": "482" + "x": "266.66666666666663", + "y": "514", + "width":"66.66666666666667" }, { - "x": "500", - "y": "0" + "x": "533.3333333333334", + "y": "0", + "width":"66.66666666666667" } ], [ { - "x": "500", - "y": "536" + "x": "533.3333333333334", + "y": "571", + "width":"66.66666666666667" } ] ]); }); } ); - /* describePlot( + describePlot( "ELEMENT.INTERVAL WITH TWO LINEAR AXIS", { unit: { @@ -220,7 +135,7 @@ define(function (require) { function () { it("should group contain interval element", function () { expectCoordsElement(expect, [ - // generate with help generateCoordIfChangeDesign + //generate with help generateCoordIfChangeDesign [ { @@ -268,7 +183,7 @@ define(function (require) { function () { it("should group contain interval element", function () { expectCoordsElement(expect, [ - // generate with help generateCoordIfChangeDesign + //generate with help generateCoordIfChangeDesign [ { @@ -324,7 +239,7 @@ define(function (require) { }); it("should group contain interval element", function () { expectCoordsElement(expect, [ - // generate with help generateCoordIfChangeDesign + //generate with help generateCoordIfChangeDesign [ { "x": "229", @@ -375,7 +290,7 @@ define(function (require) { function () { it("should group contain interval element", function () { expectCoordsElement(expect, [ - // generate with help generateCoordIfChangeDesign + //generate with help generateCoordIfChangeDesign [ { @@ -424,7 +339,7 @@ define(function (require) { function () { it("should group contain interval element", function () { expectCoordsElement(expect, [ - // generate with help generateCoordIfChangeDesign + //generate with help generateCoordIfChangeDesign [ { @@ -502,27 +417,29 @@ define(function (require) { }, dataWithDate, function () { - /!* it("should group contain interval element", function () { - expectCoordsElement(expect, [ - // generate with help generateCoordIfChangeDesign - - [ - { - "x": "0", - "y": "43" - }, - { - "x": "514.2857", - "y": "591" - }, - { - "x": "780.9524", - "y": "788" - } - ] - - ]); - });*!/ + it("should group contain interval element", function () { + expectCoordsElement(expect, [ + //generate with help generateCoordIfChangeDesign + + + [ + { + "x": "0", + "y": "43" + }, + { + "x": "514.2857", + "y": "591" + }, + { + "x": "780.9524", + "y": "788" + } + ] + + + ]); + }); } ); @@ -560,25 +477,25 @@ define(function (require) { }, dataWithDate, function () { - /!* it("should group contain interval element", function () { - expectCoordsElement(expect, [ - // generate with help generateCoordIfChangeDesign - [ - { - "x": "0", - "y": "780.9524" - }, - { - "x": "0", - "y": "266.6667" - }, - { - "x": "0", - "y": "-0.000000001" - } - ] - ]); - });*!/ + it("should group contain interval element", function () { + expectCoordsElement(expect, [ + //generate with help generateCoordIfChangeDesign + [ + { + "x": "0", + "y": "780.9524" + }, + { + "x": "0", + "y": "266.6667" + }, + { + "x": "0", + "y": "-0.000000001" + } + ] + ]); + }); } ); @@ -615,26 +532,26 @@ define(function (require) { {time: testUtils.toLocalDate('2014-02-01'), count: 10} ], function () { - /!* it("should group contain interval element", function () { - - var minimalHeight = 1; - - var coords = [ - [ - 800, - 400, - minimalHeight - ] - ]; - - var bars = getGroupBar(); - - _.each(bars, function (bar, barIndex) { - _.each(bar.childNodes, function (el, elIndex) { - expect(parseFloat(attrib(el, 'height'))).to.equal(coords[barIndex][elIndex]); - }); - }); - });*!/ + it("should group contain interval element", function () { + + var minimalHeight = 1; + + var coords = [ + [ + 800, + 400, + minimalHeight + ] + ]; + + var bars = getGroupBar(); + + _.each(bars, function (bar, barIndex) { + _.each(bar.childNodes, function (el, elIndex) { + expect(parseFloat(attrib(el, 'height'))).to.equal(coords[barIndex][elIndex]); + }); + }); + }); }); describePlot( @@ -671,26 +588,26 @@ define(function (require) { {time: testUtils.toLocalDate('2014-02-01'), count: 10} ], function () { - /!* it("should group contain interval element", function () { - - var minimalHeight = 1; - - var coords = [ - [ - 800, - 400, - minimalHeight - ] - ]; - - var bars = getGroupBar(); - - _.each(bars, function (bar, barIndex) { - _.each(bar.childNodes, function (el, elIndex) { - expect(parseFloat(attrib(el, 'width'))).to.equal(coords[barIndex][elIndex]); - }); - }); - });*!/ + it("should group contain interval element", function () { + + var minimalHeight = 1; + + var coords = [ + [ + 800, + 400, + minimalHeight + ] + ]; + + var bars = getGroupBar(); + + _.each(bars, function (bar, barIndex) { + _.each(bar.childNodes, function (el, elIndex) { + expect(parseFloat(attrib(el, 'width'))).to.equal(coords[barIndex][elIndex]); + }); + }); + }); }); describePlot( @@ -730,43 +647,43 @@ define(function (require) { {time: testUtils.toLocalDate('2014-02-07'), count: -1000} ], function () { - /!*it("should group contain interval element", function () { - - var minimalHeight = 1; - - var coords = [ - [ - 400, - 200, - minimalHeight, - 0, - minimalHeight, - 200, - 400 - ] - ]; - - var ys = [ - [ - 0, // count = 1000 - 200, // count = 500 - 399, // count = 1 (minus minimal height) - 400, // count = 0 - 400, // count = -1 - 400, // count = -500 - 400 // count = -1000 - ] - ]; - - var bars = getGroupBar(); - - _.each(bars, function (bar, barIndex) { - _.each(bar.childNodes, function (el, elIndex) { - expect(parseFloat(attrib(el, 'y'))).to.equal(ys[barIndex][elIndex]); - expect(parseFloat(attrib(el, 'height'))).to.equal(coords[barIndex][elIndex]); - }); - }); - });*!/ + it("should group contain interval element", function () { + + var minimalHeight = 1; + + var coords = [ + [ + 400, + 200, + minimalHeight, + 0, + minimalHeight, + 200, + 400 + ] + ]; + + var ys = [ + [ + 0, // count = 1000 + 200, // count = 500 + 399, // count = 1 (minus minimal height) + 400, // count = 0 + 400, // count = -1 + 400, // count = -500 + 400 // count = -1000 + ] + ]; + + var bars = getGroupBar(); + + _.each(bars, function (bar, barIndex) { + _.each(bar.childNodes, function (el, elIndex) { + expect(parseFloat(attrib(el, 'y'))).to.equal(ys[barIndex][elIndex]); + expect(parseFloat(attrib(el, 'height'))).to.equal(coords[barIndex][elIndex]); + }); + }); + }); }); describePlot( @@ -807,43 +724,43 @@ define(function (require) { {time: testUtils.toLocalDate('2014-02-07'), count: -1000} ], function () { - /!* it("should group contain interval element", function () { - - var minimalHeight = 1; - - var coords = [ - [ - 400, - 200, - minimalHeight, - 0, - minimalHeight, - 200, - 400 - ] - ]; - - var xs = [ - [ - 400, // count = 1000 - 400, // count = 500 - 400, // count = 1 - 400, // count = 0 - 399, // count = -1 (minus minimal height) - 200, // count = -500 - 0 // count = -1000 - ] - ]; - - var bars = getGroupBar(); - - _.each(bars, function (bar, barIndex) { - _.each(bar.childNodes, function (el, elIndex) { - expect(parseFloat(attrib(el, 'x'))).to.equal(xs[barIndex][elIndex]); - expect(parseFloat(attrib(el, 'width'))).to.equal(coords[barIndex][elIndex]); - }); - }); - });*!/ + it("should group contain interval element", function () { + + var minimalHeight = 1; + + var coords = [ + [ + 400, + 200, + minimalHeight, + 0, + minimalHeight, + 200, + 400 + ] + ]; + + var xs = [ + [ + 400, // count = 1000 + 400, // count = 500 + 400, // count = 1 + 400, // count = 0 + 399, // count = -1 (minus minimal height) + 200, // count = -500 + 0 // count = -1000 + ] + ]; + + var bars = getGroupBar(); + + _.each(bars, function (bar, barIndex) { + _.each(bar.childNodes, function (el, elIndex) { + expect(parseFloat(attrib(el, 'x'))).to.equal(xs[barIndex][elIndex]); + expect(parseFloat(attrib(el, 'width'))).to.equal(coords[barIndex][elIndex]); + }); + }); + }); }); describeChart("interval width for facet", @@ -903,17 +820,17 @@ define(function (require) { y: "5" }], function (context) { - it('test position', function () { - var svg = context.chart.getSVG(); - var offsets = _.map(svg.querySelectorAll('.i-role-bar-group'), function (item) { - return item.getAttribute('transform'); + it('test position',function(){ + var svg = context.chart.getSVG(); + var offsets = _.map(svg.querySelectorAll('.i-role-bar-group'), function (item) { + return item.getAttribute('transform'); + }); + expect(offsets).to.eql(["translate(66.66666666666667,0)"]); }); - expect(offsets).to.eql(["translate(66.66666666666667,0)"]); - }); }, { autoWidth: false } - );*/ + ); }); \ No newline at end of file diff --git a/test/utils/schemes.js b/test/utils/schemes.js index 227bda977..45cdfdd13 100644 --- a/test/utils/schemes.js +++ b/test/utils/schemes.js @@ -25,6 +25,14 @@ define(['js-schema'], function (schema) { y: [String] }); + var intervalGPL = schema({ + color: [null, String], + flip: [null, Boolean], + type: 'INTERVAL', + x: [String], + y: [String] + }); + var dimension = schema({ type: String, scale: String @@ -34,15 +42,6 @@ define(['js-schema'], function (schema) { '*': [null, dimension] }; - var scale = schema({ - type: String, - scale: String - }); - - var scales = { - '*': [null, scale] - }; - var scatterplot = schema({ dimensions: dimensions, unit: schema({ @@ -56,13 +55,29 @@ define(['js-schema'], function (schema) { }); var bar = schema({ - scales: scales, + dimensions: dimensions, unit: schema({ guide: undefined, x: [null, String], y: [null, String], type: 'COORDS.RECT', - units: Array.of(interval) + unit: Array.of(interval) + + }) + }); + + var scale = schema({type: String, scale: String}); + var scales = { + '*': [null, scale] + }; + var barGPL = schema({ + scales: scales, + unit: schema({ + guide: undefined, + x: [null, String], + y: [null, String], + type: 'RECT', + units: Array.of(intervalGPL) }) }); @@ -86,10 +101,11 @@ define(['js-schema'], function (schema) { schemes.point = point; schemes.interval = interval; schemes.bar = bar; + schemes.barGPL = barGPL; schemes.line = lineSpec; schemes.scatterplot = scatterplot; schemes.config = config; }(schemes)); return schemes; -}); +}); \ No newline at end of file From 01398e05e6f391afd475da69ed311971008953f3 Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Tue, 3 Mar 2015 16:19:35 +0300 Subject: [PATCH 40/93] jscs errors are fixed --- test/element-interval.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/element-interval.test.js b/test/element-interval.test.js index db4bd0b36..56c39bb08 100644 --- a/test/element-interval.test.js +++ b/test/element-interval.test.js @@ -1,3 +1,5 @@ +// jscs:disable disallowQuotedKeysInObjects +// jscs:disable validateQuoteMarks define(function (require) { var expect = require('chai').expect; var schemes = require('schemes'); From 8a04aebb6b47816ec95ebaddd53feb3e17dca9b1 Mon Sep 17 00:00:00 2001 From: konstantin Date: Tue, 3 Mar 2015 16:49:39 +0300 Subject: [PATCH 41/93] inject UnitRegistry to GPL --- examples/streaming.html | 6 +- src/charts/tau.gpl.js | 4 +- src/charts/tau.plot.js | 4 +- src/dsl-reader.js | 8 +- src/elements/element.interval.fn.js | 2 +- src/elements/element.interval.js | 1 - src/tau.charts.js | 6 +- src/units-registry.js | 4 +- test/dsl-reader-layout.test.js | 18 +- test/dsl-reader.test.js | 8 +- test/element-interval.test.js | 1488 ++++++++++++++------------- test/tests-main.js | 2 +- 12 files changed, 821 insertions(+), 730 deletions(-) diff --git a/examples/streaming.html b/examples/streaming.html index d24eb3c8c..761732f05 100644 --- a/examples/streaming.html +++ b/examples/streaming.html @@ -235,7 +235,7 @@ 'team': {type: 'ordinal', source: '/', dim: 'team'} }, unit: { - type: 'RECT', + type: 'COORDS.RECT', x: 'project', y: 'team', expression: { @@ -245,7 +245,7 @@ params: ['project', 'team'] }, units: [{ - type: 'RECT', + type: 'COORDS.RECT', x: 'story', y: 'count', expression: { @@ -262,7 +262,7 @@ }, units: [ { - type: 'INTERVAL', + type: 'ELEMENT.INTERVAL', x: 'story', y: 'count', color: 'color', diff --git a/src/charts/tau.gpl.js b/src/charts/tau.gpl.js index d0c7cbcf6..8182c1217 100644 --- a/src/charts/tau.gpl.js +++ b/src/charts/tau.gpl.js @@ -1,7 +1,7 @@ import {Emitter} from '../event'; import {utils} from '../utils/utils'; import {utilsDom} from '../utils/utils-dom'; -import {UnitsRegistry} from '../units-registry'; +import {unitsRegistry} from '../units-registry'; import {getLayout} from '../utils/layuot-template'; import {ScalesFactory} from '../scales-factory'; import {CSS_PREFIX} from '../const'; @@ -41,7 +41,7 @@ export class GPL extends Emitter { this.config = config; - this.unitSet = UnitsRegistry; + this.unitSet = config.unitsRegistry || unitsRegistry; this.sources = config.sources; diff --git a/src/charts/tau.plot.js b/src/charts/tau.plot.js index 14812a634..9118084c3 100644 --- a/src/charts/tau.plot.js +++ b/src/charts/tau.plot.js @@ -8,7 +8,7 @@ import {utils} from '../utils/utils'; import {utilsDom} from '../utils/utils-dom'; import {CSS_PREFIX} from '../const'; import {UnitDomainMixin} from '../unit-domain-mixin'; -import {UnitsRegistry} from '../units-registry'; +import {unitsRegistry} from '../units-registry'; import {DataProcessor} from '../data-processor'; import {getLayout} from '../utils/layuot-template'; @@ -135,7 +135,7 @@ export class Plot extends Emitter { var optimalSize = this.config.settings.size; - var reader = new DSLReader(domainMixin, UnitsRegistry); + var reader = new DSLReader(domainMixin, unitsRegistry); var chart = this; var logicXGraph = reader.buildGraph(fullSpec); diff --git a/src/dsl-reader.js b/src/dsl-reader.js index 43d5625aa..6aa1753ca 100644 --- a/src/dsl-reader.js +++ b/src/dsl-reader.js @@ -1,12 +1,12 @@ export class DSLReader { - constructor(domainMixin, UnitsRegistry) { + constructor(domainMixin, unitsRegistry) { this.domain = domainMixin; - this.UnitsRegistry = UnitsRegistry; + this.unitsRegistry = unitsRegistry; } buildGraph(spec) { - var buildRecursively = (unit) => this.UnitsRegistry + var buildRecursively = (unit) => this.unitsRegistry .get(unit.type) .walk(this.domain.mix(unit), buildRecursively); return buildRecursively(spec.unit); @@ -77,7 +77,7 @@ export class DSLReader { styledGraph.options.container = target; var renderRecursively = (unit) => { var unitMeta = this.domain.mix(unit); - var subSpace = this.UnitsRegistry.get(unit.type).draw(unitMeta); + var subSpace = this.unitsRegistry.get(unit.type).draw(unitMeta); var children = unit.childUnits || []; children.forEach((child) => { diff --git a/src/elements/element.interval.fn.js b/src/elements/element.interval.fn.js index 573f67be2..d58d0336c 100644 --- a/src/elements/element.interval.fn.js +++ b/src/elements/element.interval.fn.js @@ -12,7 +12,7 @@ var getSizesParams = (params) => { offsetCategory: intervalWidth }; }; -var isMeasure = (dim)=> dim.dimType === 'measure'; +var isMeasure = (dim)=> dim.scaleType === 'linear'; var flipHub = { NORM: ({colorScale, node, xScale, yScale, colorIndexScale, width, height, defaultSizeParams}) => { let minimalHeight = 1; diff --git a/src/elements/element.interval.js b/src/elements/element.interval.js index 78107fbe1..f5c258743 100644 --- a/src/elements/element.interval.js +++ b/src/elements/element.interval.js @@ -12,7 +12,6 @@ export class Interval { drawLayout(fnCreateScale) { var config = this.config; - this.xScale = fnCreateScale('pos', config.x, [0, config.options.width]); this.yScale = fnCreateScale('pos', config.y, [config.options.height, 0]); this.color = fnCreateScale('color', config.color, {}); diff --git a/src/tau.charts.js b/src/tau.charts.js index 1323b8eca..b431bc73b 100644 --- a/src/tau.charts.js +++ b/src/tau.charts.js @@ -9,7 +9,7 @@ import {SpecEngineFactory} from './spec-engine-factory'; import {LayoutEngineFactory} from './layout-engine-factory'; import {FormatterRegistry} from './formatter-registry'; import {nodeMap} from './node-map'; -import {UnitsRegistry} from './units-registry'; +import {unitsRegistry} from './units-registry'; import {Cartesian} from './elements/coords.cartesian'; import {Point} from './elements/element.point'; @@ -28,7 +28,7 @@ var __api__ = { LayoutEngineFactory: LayoutEngineFactory }; var api = { - UnitsRegistry: UnitsRegistry, + unitsRegistry: unitsRegistry, tickFormat: FormatterRegistry, d3: d3, _: _, @@ -116,7 +116,7 @@ var api = { Plot.globalSettings = api.globalSettings; -api.UnitsRegistry +api.unitsRegistry .add('COORDS.PARALLEL', nodeMap['COORDS.PARALLEL']) .add('PARALLEL/ELEMENT.LINE', nodeMap['PARALLEL/ELEMENT.LINE']) .add('COORDS.RECT', nodeMap['COORDS.RECT']) diff --git a/src/units-registry.js b/src/units-registry.js index 99458b260..40a201109 100644 --- a/src/units-registry.js +++ b/src/units-registry.js @@ -1,6 +1,6 @@ var UnitsMap = {}; -var UnitsRegistry = { +var unitsRegistry = { reg: function (unitType, xUnit) { UnitsMap[unitType] = xUnit; @@ -25,4 +25,4 @@ var UnitsRegistry = { } }; -export {UnitsRegistry}; \ No newline at end of file +export {unitsRegistry}; \ No newline at end of file diff --git a/test/dsl-reader-layout.test.js b/test/dsl-reader-layout.test.js index fee584ace..ff1caa62a 100644 --- a/test/dsl-reader-layout.test.js +++ b/test/dsl-reader-layout.test.js @@ -4,7 +4,7 @@ define(function (require) { var schemes = require('schemes'); var tauChart = require('tau_modules/tau.charts'); var UnitDomainMixin = require('tau_modules/unit-domain-mixin').UnitDomainMixin; - var UnitsRegistry = require('tau_modules/units-registry').UnitsRegistry; + var unitsRegistry = require('tau_modules/units-registry').unitsRegistry; function globalChartSettings() { return _.defaults({ fitSize: false }, testUtils.chartSettings); @@ -55,7 +55,7 @@ define(function (require) { var domainMixin = new UnitDomainMixin(spec.dimensions, data); var api = tauChart.__api__; - var reader = new api.DSLReader(domainMixin, UnitsRegistry); + var reader = new api.DSLReader(domainMixin, unitsRegistry); var fullSpec = api.SpecEngineFactory.get('NONE', globalChartSettings())(spec, domainMixin.mix({})); @@ -90,7 +90,7 @@ define(function (require) { var domainMixin = new UnitDomainMixin(spec.dimensions, data); var api = tauChart.__api__; - var reader = new api.DSLReader(domainMixin, UnitsRegistry); + var reader = new api.DSLReader(domainMixin, unitsRegistry); var fullSpec = api.SpecEngineFactory.get('NONE', globalChartSettings())(spec, domainMixin.mix({})); var logicXGraph = reader.buildGraph(fullSpec); @@ -166,7 +166,7 @@ define(function (require) { var domainMixin = new UnitDomainMixin(spec.dimensions, data); var api = tauChart.__api__; - var reader = new api.DSLReader(domainMixin, UnitsRegistry); + var reader = new api.DSLReader(domainMixin, unitsRegistry); var fullSpec = api.SpecEngineFactory.get('NONE', globalChartSettings())(spec, domainMixin.mix({})); var logicXGraph = reader.buildGraph(fullSpec); @@ -243,7 +243,7 @@ define(function (require) { var domainMixin = new UnitDomainMixin(spec.dimensions, data); var api = tauChart.__api__; - var reader = new api.DSLReader(domainMixin, UnitsRegistry); + var reader = new api.DSLReader(domainMixin, unitsRegistry); var fullSpec = api.SpecEngineFactory.get('NONE', globalChartSettings())(spec, domainMixin.mix({})); var logicXGraph = reader.buildGraph(fullSpec); @@ -321,7 +321,7 @@ define(function (require) { var domainMixin = new UnitDomainMixin(spec.dimensions, data); var api = tauChart.__api__; - var reader = new api.DSLReader(domainMixin, UnitsRegistry); + var reader = new api.DSLReader(domainMixin, unitsRegistry); var fullSpec = api.SpecEngineFactory.get('NONE', globalChartSettings())(spec, domainMixin.mix({})); var logicXGraph = reader.buildGraph(fullSpec); @@ -411,7 +411,7 @@ define(function (require) { var domainMixin = new UnitDomainMixin(spec.dimensions, data); var api = tauChart.__api__; - var reader = new api.DSLReader(domainMixin, UnitsRegistry); + var reader = new api.DSLReader(domainMixin, unitsRegistry); var fullSpec = api.SpecEngineFactory.get('NONE', globalChartSettings())(spec, domainMixin.mix({})); var logicXGraph = reader.buildGraph(fullSpec); @@ -490,7 +490,7 @@ define(function (require) { var domainMixin = new UnitDomainMixin(spec.dimensions, data); var api = tauChart.__api__; - var reader = new api.DSLReader(domainMixin, UnitsRegistry); + var reader = new api.DSLReader(domainMixin, unitsRegistry); var fullSpec = api.SpecEngineFactory.get('NONE', globalChartSettings())(spec, domainMixin.mix({})); var logicXGraph = reader.buildGraph(fullSpec); @@ -581,7 +581,7 @@ define(function (require) { var domainMixin = new UnitDomainMixin(spec.dimensions, data); var api = tauChart.__api__; - var reader = new api.DSLReader(domainMixin, UnitsRegistry); + var reader = new api.DSLReader(domainMixin, unitsRegistry); var fullSpec = api.SpecEngineFactory.get('NONE', globalChartSettings())(spec, domainMixin.mix({})); var logicXGraph = reader.buildGraph(fullSpec); diff --git a/test/dsl-reader.test.js b/test/dsl-reader.test.js index ba04e1bfc..b595e1d6c 100644 --- a/test/dsl-reader.test.js +++ b/test/dsl-reader.test.js @@ -4,7 +4,7 @@ define(function (require) { var schemes = require('schemes'); var tauChart = require('tau_modules/tau.charts'); var UnitDomainMixin = require('tau_modules/unit-domain-mixin').UnitDomainMixin; - var UnitsRegistry = require('tau_modules/units-registry').UnitsRegistry; + var unitsRegistry = require('tau_modules/units-registry').unitsRegistry; function globalChartSettings() { return _.defaults({ fitSize: false }, testUtils.chartSettings); @@ -68,7 +68,7 @@ define(function (require) { var originalSpecState = JSON.stringify(spec); var api = tauChart.__api__; - var reader = new api.DSLReader(domainMixin, UnitsRegistry); + var reader = new api.DSLReader(domainMixin, unitsRegistry); var fullSpec = api.SpecEngineFactory.get('NONE', globalChartSettings())(spec, domainMixin.mix({})); var logicalGraph = reader.buildGraph(fullSpec); @@ -121,7 +121,7 @@ define(function (require) { var domainMixin = new UnitDomainMixin(spec.dimensions, data); var api = tauChart.__api__; - var reader = new api.DSLReader(domainMixin, UnitsRegistry); + var reader = new api.DSLReader(domainMixin, unitsRegistry); var fullSpec = api.SpecEngineFactory.get('NONE', globalChartSettings())(spec, domainMixin.mix({})); var logicalGraph = reader.buildGraph(fullSpec); @@ -166,7 +166,7 @@ define(function (require) { var domainMixin = new UnitDomainMixin(spec.dimensions, data); var api = tauChart.__api__; - var reader = new api.DSLReader(domainMixin, UnitsRegistry); + var reader = new api.DSLReader(domainMixin, unitsRegistry); var fullSpec = api.SpecEngineFactory.get('NONE', globalChartSettings())(spec, domainMixin.mix({})); var logicalGraph = reader.buildGraph(fullSpec); diff --git a/test/element-interval.test.js b/test/element-interval.test.js index 56c39bb08..c35aaa749 100644 --- a/test/element-interval.test.js +++ b/test/element-interval.test.js @@ -4,7 +4,7 @@ define(function (require) { var expect = require('chai').expect; var schemes = require('schemes'); var assert = require('chai').assert; - var tauChart = require('tau_modules/tau.charts').tauChart; + var tauCharts = require('tau_modules/tau.charts').tauCharts; var testUtils = require('testUtils'); var getGroupBar = testUtils.getGroupBar; var attrib = testUtils.attrib; @@ -26,8 +26,94 @@ define(function (require) { }); return coords[0]; }*/ + var convertSpec = function (spec, data) { + var unit = spec.unit; + return { + sources: { + '?': { + dims: {}, + data: [] + }, + '/': { + dims: { + x: {type: 'category'}, + y: {type: 'measure'}, + color: {type: 'category'} + }, + data: data + } + }, + trans: { + where: function (data, tuple) { + var predicates = _.map(tuple, function (v, k) { + return function (row) { + return (row[k] === v); + } + }); + return _(data).filter(function (row) { + return _.every(predicates, function (p) { + return p(row); + }) + }); + } + }, + scales: _.defaults(spec.scales || {}, { + 'x': {type: 'ordinal', source: '/', dim: 'x'}, + 'y': {type: 'linear', source: '/', dim: 'y'}, + 'size:default': {type: 'size', source: '?', mid: 5}, + 'color': {type: 'color', dim: 'color', source: '/'}, + 'color:default': {type: 'color', source: '?', brewer: null} + }), + unit: { + type: 'COORDS.RECT', + expression: { + inherit: false, + source: '/', + operator: 'none' + }, + x: unit.x, + y: unit.y, + units: [_.defaults(unit.units[0], { + type: 'ELEMENT.INTERVAL', + x: 'x', + y: 'y', + expression: { + inherit: true, + source: '/', + operator: 'groupBy', + params: ['color'] + } + })] + } + } + }; + var describePlot = /*testUtils.describePlot;*/ + + function d(name, spec, data, fn) { + describe(name, function () { + var context = { + element: null, + chart: null + }; + + beforeEach(function () { + context.element = document.createElement('div'); + document.body.appendChild(context.element); + + // tauCharts.Plot.globalSettings = testChartSettings; + + context.chart = new tauCharts.GPL(convertSpec(spec, data)); + + context.chart.renderTo(context.element, {width: 800, height: 800}); + }); - var describePlot = testUtils.describePlot; + fn(context); + + afterEach(function () { + context.element.parentNode.removeChild(context.element); + }); + }); + }; // testUtils.describePlot; var describeChart = testUtils.describeChart; var expectCoordsElement = function (expect, coords) { var bars = getGroupBar(); @@ -55,17 +141,19 @@ define(function (require) { type: 'COORDS.RECT', x: 'x', y: 'y', - guide: { - x: {autoScale: false}, - y: {autoScale: false} - }, - unit: [ + units: [ { type: 'ELEMENT.INTERVAL', x: 'x', flip: false, y: 'y', - color: 'color' + color: 'color', + expression: { + inherit: true, + source: '/', + operator: 'groupBy', + params: ['color'] + } } ] } @@ -83,32 +171,33 @@ define(function (require) { expect(getGroupBar().length).to.equal(3); }); it("should group contain interval element", function () { + // debugger expectCoordsElement(expect, [ //generate with help generateCoordIfChangeDesign [ { "x": "0", - "y": "457", - "width":"66.66666666666667" + "y": "429", + "width": "62.5000" } ], [ { - "x": "266.66666666666663", - "y": "514", - "width":"66.66666666666667" + "x": "250", + "y": "482", + "width": "62.5000" }, { - "x": "533.3333333333334", + "x": "500", "y": "0", - "width":"66.66666666666667" + "width": "62.5000" } ], [ { - "x": "533.3333333333334", - "y": "571", - "width":"66.66666666666667" + "x": "500", + "y": "536", + "width": "62.5000" } ] ]); @@ -116,174 +205,21 @@ define(function (require) { } ); describePlot( - "ELEMENT.INTERVAL WITH TWO LINEAR AXIS", + "ELEMENT.INTERVAL WITH X LINEAR AXIS", { unit: { type: 'COORDS.RECT', x: 'y', y: 'x', - guide: { - x: {autoScale: false}, - y: {autoScale: false} - }, - unit: [ + units: [ { - type: 'ELEMENT.INTERVAL' - } - ] - } - }, - testData, - function () { - it("should group contain interval element", function () { - expectCoordsElement(expect, [ - //generate with help generateCoordIfChangeDesign - - [ - { - "x": "340.5", - "y": "666.6666666666666" - }, - { - "x": "283.5", - "y": "399.99999999999994" - }, - { - "x": "-2.5", - "y": "133.33333333333326" - }, - { - "x": "797.5", - "y": "133.33333333333326" - } - ] - - ]); - }); - } - ); - describePlot( - "ELEMENT.INTERVAL WITH TWO CATEGORICAL AXIS", - { - unit: { - type: 'COORDS.RECT', - x: 'x', - y: 'color', - guide: { - x: {autoScale: false}, - y: {autoScale: false} - }, - unit: [ - { - type: 'ELEMENT.INTERVAL', - flip: false - } - ] - } - }, - testData, - function () { - it("should group contain interval element", function () { - expectCoordsElement(expect, [ - //generate with help generateCoordIfChangeDesign - - [ - { - "x": "0", - "y": "666.6666666666666" - }, - { - "x": "266.66666666666663", - "y": "399.99999999999994" - }, - { - "x": "533.3333333333334", - "y": "133.33333333333326" - }, - { - "x": "533.3333333333334", - "y": "399.99999999999994" - } - ] - - ]); - }); - } - ); - - describePlot("ELEMENT.INTERVAL.FLIP WITH LINEAR AND CATEGORICAL AXIS", { - unit: { - type: 'COORDS.RECT', - x: 'y', - y: 'x', - guide: { - x: {autoScale: false}, - y: {autoScale: false} - }, - unit: [ - { - type: 'ELEMENT.INTERVAL', x: 'y', - flip: true, y: 'x', - color: 'color' - } - ] - } - }, - testData, - function (context) { - - it("should render group bar element", function () { - var chart = context.chart; - assert.ok(schemes.bar(chart.config.spec), 'spec is right'); - expect(getGroupBar().length).to.equal(3); - }); - it("should group contain interval element", function () { - expectCoordsElement(expect, [ - //generate with help generateCoordIfChangeDesign - [ - { - "x": "229", - "y": "533.3333333333333" - } - ], - [ - { - "x": "229", - "y": "266.66666666666663" - }, - { - "x": "229", - "y": "-8.526512829121202e-14" + expression: { + inherit: true, + source: '/', + operator: 'none' } - ], - [ - { - "x": "0", - "y": "-8.526512829121202e-14" - } - ] - ]); - }); - } - ); - - describePlot( - "ELEMENT.INTERVAL.FLIP WITH TWO LINEAR AXIS", - { - unit: { - type: 'COORDS.RECT', - x: 'x', - y: 'y', - guide: { - x: {autoScale: false}, - y: {autoScale: false} - }, - unit: [ - { - type: 'ELEMENT.INTERVAL', - flip: true } ] } @@ -295,544 +231,700 @@ define(function (require) { //generate with help generateCoordIfChangeDesign [ - { - "x": "0", - "y": "454.5" - }, - { - "x": "0", - "y": "511.5" - }, - { - "x": "0", - "y": "797.5" - }, - { - "x": "0", - "y": "-2.5" - } + [ + { + "x": "318.5", + "y": "625" + }, + { + "x": "265.5", + "y": "375" + }, + { + "x": "-2.5", + "y": "125" + }, + { + "x": "747.5", + "y": "125" + } + ] ] ]); }); } ); + /* + describePlot( + "ELEMENT.INTERVAL WITH TWO CATEGORICAL AXIS", + { + unit: { + type: 'COORDS.RECT', + x: 'x', + y: 'color', + guide: { + x: {autoScale: false}, + y: {autoScale: false} + }, + unit: [ + { + type: 'ELEMENT.INTERVAL', + flip: false + } + ] + } + }, + testData, + function () { + it("should group contain interval element", function () { + expectCoordsElement(expect, [ + // generate with help generateCoordIfChangeDesign + + [ + { + "x": "0", + "y": "666.6666666666666" + }, + { + "x": "266.66666666666663", + "y": "399.99999999999994" + }, + { + "x": "533.3333333333334", + "y": "133.33333333333326" + }, + { + "x": "533.3333333333334", + "y": "399.99999999999994" + } + ] + + ]); + }); + } + ); + + describePlot("ELEMENT.INTERVAL.FLIP WITH LINEAR AND CATEGORICAL AXIS", { + unit: { + type: 'COORDS.RECT', + x: 'y', + y: 'x', + guide: { + x: {autoScale: false}, + y: {autoScale: false} + }, + unit: [ + { + type: 'ELEMENT.INTERVAL', + x: 'y', + flip: true, + y: 'x', + color: 'color' + } + ] + } + }, + testData, + function (context) { + + it("should render group bar element", function () { + var chart = context.chart; + assert.ok(schemes.bar(chart.config.spec), 'spec is right'); + expect(getGroupBar().length).to.equal(3); + }); + it("should group contain interval element", function () { + expectCoordsElement(expect, [ + // generate with help generateCoordIfChangeDesign + [ + { + "x": "229", + "y": "533.3333333333333" + } + ], + [ + { + "x": "229", + "y": "266.66666666666663" + }, + { + "x": "229", + "y": "-8.526512829121202e-14" + } + ], + [ + { + "x": "0", + "y": "-8.526512829121202e-14" + } + ] + ]); + }); + } + ); + + describePlot( + "ELEMENT.INTERVAL.FLIP WITH TWO LINEAR AXIS", + { + unit: { + type: 'COORDS.RECT', + x: 'x', + y: 'y', + guide: { + x: {autoScale: false}, + y: {autoScale: false} + }, + unit: [ + { + type: 'ELEMENT.INTERVAL', + flip: true + } + ] + } + }, + testData, + function () { + it("should group contain interval element", function () { + expectCoordsElement(expect, [ + // generate with help generateCoordIfChangeDesign + + [ + { + "x": "0", + "y": "454.5" + }, + { + "x": "0", + "y": "511.5" + }, + { + "x": "0", + "y": "797.5" + }, + { + "x": "0", + "y": "-2.5" + } + ] + + ]); + }); + } + ); + + describePlot( + "ELEMENT.INTERVAL.FLIP WITH TWO CATEGORICAL AXIS", + { + unit: { + type: 'COORDS.RECT', + x: 'x', + y: 'color', + guide: { + x: {autoScale: false}, + y: {autoScale: false} + }, + unit: [ + { + type: 'ELEMENT.INTERVAL', + flip: true + } + ] + } + }, + testData, + function () { + it("should group contain interval element", function () { + expectCoordsElement(expect, [ + // generate with help generateCoordIfChangeDesign + + [ + { + "x": "0", + "y": "533.3333333333333" + }, + { + "x": "0", + "y": "266.66666666666663" + }, + { + "x": "0", + "y": "-8.526512829121202e-14" + }, + { + "x": "0", + "y": "266.66666666666663" + } + ] + + ]); + }); + } + ); + var offsetHrs = new Date().getTimezoneOffset() / 60; + var offsetISO = '0' + Math.abs(offsetHrs) + ':00'; + var iso = function (str) { + return (str + '+' + offsetISO); + }; + var dataWithDate = [ + { + "createDate": new Date(iso("2014-09-02T21:00:00")), + "count": 123 + }, + { + "createDate": new Date(iso("2014-09-29T21:00:00")), + "count": 34 + }, + { + "createDate": new Date(iso("2014-10-13T21:00:00")), + "count": 2 + } + ]; + + describePlot( + "ELEMENT.INTERVAL WITH TWO ORDER AXIS", + { + dimensions: { + "createDate": { + "type": "order", + "scale": "period" + }, + "count": { + "type": "measure" + } + }, + unit: { + type: 'COORDS.RECT', + x: 'createDate', + y: 'count', + guide: { + "x": { + "label": "Create Date", + "autoScale": true, + "tickFormat": "%j", + "tickPeriod": "day" + } + }, + unit: [ + { + type: 'ELEMENT.INTERVAL' + } + ] + } + }, + dataWithDate, + function () { + /!* it("should group contain interval element", function () { + expectCoordsElement(expect, [ + // generate with help generateCoordIfChangeDesign + + [ + { + "x": "0", + "y": "43" + }, + { + "x": "514.2857", + "y": "591" + }, + { + "x": "780.9524", + "y": "788" + } + ] + + ]); + });*!/ + } + ); + + describePlot( + "ELEMENT.INTERVAL.FLIP WITH TWO ORDER AXIS", + { + dimensions: { + "createDate": { + "type": "order", + "scale": "period" + }, + "count": { + "type": "measure" + } + }, + unit: { + type: 'COORDS.RECT', + y: 'createDate', + x: 'count', + guide: { + "y": { + "label": "Create Date", + "autoScale": true, + "tickPeriod": "day", + "tickFormat": "%j" + } + }, + unit: [ + { + type: 'ELEMENT.INTERVAL', + flip: true + } + ] + } + }, + dataWithDate, + function () { + /!* it("should group contain interval element", function () { + expectCoordsElement(expect, [ + // generate with help generateCoordIfChangeDesign + [ + { + "x": "0", + "y": "780.9524" + }, + { + "x": "0", + "y": "266.6667" + }, + { + "x": "0", + "y": "-0.000000001" + } + ] + ]); + });*!/ + } + ); + + describePlot( + "ELEMENT.INTERVAL WITH MEASURE (:time) as X / MEASURE (:number) AXIS as Y", + { + dimensions: { + "time": { + "type": "measure", + "scale": "time" + }, + "count": { + "type": "measure" + } + }, + unit: { + type: 'COORDS.RECT', + x: 'count', + y: 'time', + guide: { + x: {autoScale: false}, + y: {autoScale: false} + }, + unit: [ + { + type: 'ELEMENT.INTERVAL' + } + ] + } + }, + [ + {time: testUtils.toLocalDate('2014-02-03'), count: 0}, + {time: testUtils.toLocalDate('2014-02-02'), count: 5}, + {time: testUtils.toLocalDate('2014-02-01'), count: 10} + ], + function () { + /!* it("should group contain interval element", function () { + + var minimalHeight = 1; + + var coords = [ + [ + 800, + 400, + minimalHeight + ] + ]; - describePlot( - "ELEMENT.INTERVAL.FLIP WITH TWO CATEGORICAL AXIS", - { - unit: { - type: 'COORDS.RECT', - x: 'x', - y: 'color', - guide: { - x: {autoScale: false}, - y: {autoScale: false} - }, - unit: [ - { - type: 'ELEMENT.INTERVAL', - flip: true - } - ] - } - }, - testData, - function () { - it("should group contain interval element", function () { - expectCoordsElement(expect, [ - //generate with help generateCoordIfChangeDesign - - [ - { - "x": "0", - "y": "533.3333333333333" - }, - { - "x": "0", - "y": "266.66666666666663" - }, - { - "x": "0", - "y": "-8.526512829121202e-14" - }, - { - "x": "0", - "y": "266.66666666666663" - } - ] - - ]); - }); - } - ); - var offsetHrs = new Date().getTimezoneOffset() / 60; - var offsetISO = '0' + Math.abs(offsetHrs) + ':00'; - var iso = function (str) { - return (str + '+' + offsetISO); - }; - var dataWithDate = [ - { - "createDate": new Date(iso("2014-09-02T21:00:00")), - "count": 123 - }, - { - "createDate": new Date(iso("2014-09-29T21:00:00")), - "count": 34 - }, - { - "createDate": new Date(iso("2014-10-13T21:00:00")), - "count": 2 - } - ]; - - describePlot( - "ELEMENT.INTERVAL WITH TWO ORDER AXIS", - { - dimensions: { - "createDate": { - "type": "order", - "scale": "period" - }, - "count": { - "type": "measure" - } - }, - unit: { - type: 'COORDS.RECT', - x: 'createDate', - y: 'count', - guide: { - "x": { - "label": "Create Date", - "autoScale": true, - "tickFormat": "%j", - "tickPeriod": "day" - } - }, - unit: [ - { - type: 'ELEMENT.INTERVAL' - } - ] - } - }, - dataWithDate, - function () { - it("should group contain interval element", function () { - expectCoordsElement(expect, [ - //generate with help generateCoordIfChangeDesign - - - [ - { - "x": "0", - "y": "43" - }, - { - "x": "514.2857", - "y": "591" - }, - { - "x": "780.9524", - "y": "788" - } - ] - - - ]); - }); - } - ); - - describePlot( - "ELEMENT.INTERVAL.FLIP WITH TWO ORDER AXIS", - { - dimensions: { - "createDate": { - "type": "order", - "scale": "period" - }, - "count": { - "type": "measure" - } - }, - unit: { - type: 'COORDS.RECT', - y: 'createDate', - x: 'count', - guide: { - "y": { - "label": "Create Date", - "autoScale": true, - "tickPeriod": "day", - "tickFormat": "%j" - } - }, - unit: [ - { - type: 'ELEMENT.INTERVAL', - flip: true - } - ] - } - }, - dataWithDate, - function () { - it("should group contain interval element", function () { - expectCoordsElement(expect, [ - //generate with help generateCoordIfChangeDesign - [ - { - "x": "0", - "y": "780.9524" - }, - { - "x": "0", - "y": "266.6667" - }, - { - "x": "0", - "y": "-0.000000001" - } - ] - ]); - }); - } - ); - - describePlot( - "ELEMENT.INTERVAL WITH MEASURE (:time) as X / MEASURE (:number) AXIS as Y", - { - dimensions: { - "time": { - "type": "measure", - "scale": "time" - }, - "count": { - "type": "measure" - } - }, - unit: { - type: 'COORDS.RECT', - x: 'count', - y: 'time', - guide: { - x: {autoScale: false}, - y: {autoScale: false} - }, - unit: [ - { - type: 'ELEMENT.INTERVAL' - } - ] - } - }, - [ - {time: testUtils.toLocalDate('2014-02-03'), count: 0}, - {time: testUtils.toLocalDate('2014-02-02'), count: 5}, - {time: testUtils.toLocalDate('2014-02-01'), count: 10} - ], - function () { - it("should group contain interval element", function () { - - var minimalHeight = 1; - - var coords = [ - [ - 800, - 400, - minimalHeight - ] - ]; - - var bars = getGroupBar(); - - _.each(bars, function (bar, barIndex) { - _.each(bar.childNodes, function (el, elIndex) { - expect(parseFloat(attrib(el, 'height'))).to.equal(coords[barIndex][elIndex]); - }); - }); - }); - }); - - describePlot( - "ELEMENT.INTERVAL.FLIP WITH MEASURE (:number) AXIS as X / MEASURE (:time) as Y", - { - dimensions: { - "time": { - "type": "measure", - "scale": "time" - }, - "count": { - "type": "measure" - } - }, - unit: { - type: 'COORDS.RECT', - y: 'count', - x: 'time', - guide: { - x: {autoScale: false}, - y: {autoScale: false} - }, - unit: [ - { - type: 'ELEMENT.INTERVAL', - flip: true - } - ] - } - }, - [ - {time: testUtils.toLocalDate('2014-02-03'), count: 0}, - {time: testUtils.toLocalDate('2014-02-02'), count: 5}, - {time: testUtils.toLocalDate('2014-02-01'), count: 10} - ], - function () { - it("should group contain interval element", function () { - - var minimalHeight = 1; - - var coords = [ - [ - 800, - 400, - minimalHeight - ] - ]; - - var bars = getGroupBar(); - - _.each(bars, function (bar, barIndex) { - _.each(bar.childNodes, function (el, elIndex) { - expect(parseFloat(attrib(el, 'width'))).to.equal(coords[barIndex][elIndex]); - }); - }); - }); - }); - - describePlot( - "ELEMENT.INTERVAL WITH MEASURE (:time) AXIS as X / MEASURE (:number) as Y", - { - dimensions: { - "time": { - "type": "measure", - "scale": "time" - }, - "count": { - "type": "measure" - } - }, - unit: { - type: 'COORDS.RECT', - y: 'count', - x: 'time', - guide: { - x: {autoScale: false}, - y: {autoScale: false} - }, - unit: [ - { - type: 'ELEMENT.INTERVAL' - } - ] - } - }, - [ - {time: testUtils.toLocalDate('2014-02-01'), count: 1000}, - {time: testUtils.toLocalDate('2014-02-02'), count: 500}, - {time: testUtils.toLocalDate('2014-02-03'), count: 1}, - {time: testUtils.toLocalDate('2014-02-04'), count: 0}, - {time: testUtils.toLocalDate('2014-02-05'), count: -1}, - {time: testUtils.toLocalDate('2014-02-06'), count: -500}, - {time: testUtils.toLocalDate('2014-02-07'), count: -1000} - ], - function () { - it("should group contain interval element", function () { - - var minimalHeight = 1; - - var coords = [ - [ - 400, - 200, - minimalHeight, - 0, - minimalHeight, - 200, - 400 - ] - ]; + var bars = getGroupBar(); - var ys = [ - [ - 0, // count = 1000 - 200, // count = 500 - 399, // count = 1 (minus minimal height) - 400, // count = 0 - 400, // count = -1 - 400, // count = -500 - 400 // count = -1000 - ] - ]; + _.each(bars, function (bar, barIndex) { + _.each(bar.childNodes, function (el, elIndex) { + expect(parseFloat(attrib(el, 'height'))).to.equal(coords[barIndex][elIndex]); + }); + }); + });*!/ + }); - var bars = getGroupBar(); + describePlot( + "ELEMENT.INTERVAL.FLIP WITH MEASURE (:number) AXIS as X / MEASURE (:time) as Y", + { + dimensions: { + "time": { + "type": "measure", + "scale": "time" + }, + "count": { + "type": "measure" + } + }, + unit: { + type: 'COORDS.RECT', + y: 'count', + x: 'time', + guide: { + x: {autoScale: false}, + y: {autoScale: false} + }, + unit: [ + { + type: 'ELEMENT.INTERVAL', + flip: true + } + ] + } + }, + [ + {time: testUtils.toLocalDate('2014-02-03'), count: 0}, + {time: testUtils.toLocalDate('2014-02-02'), count: 5}, + {time: testUtils.toLocalDate('2014-02-01'), count: 10} + ], + function () { + /!* it("should group contain interval element", function () { + + var minimalHeight = 1; + + var coords = [ + [ + 800, + 400, + minimalHeight + ] + ]; - _.each(bars, function (bar, barIndex) { - _.each(bar.childNodes, function (el, elIndex) { - expect(parseFloat(attrib(el, 'y'))).to.equal(ys[barIndex][elIndex]); - expect(parseFloat(attrib(el, 'height'))).to.equal(coords[barIndex][elIndex]); - }); - }); - }); - }); + var bars = getGroupBar(); - describePlot( - "ELEMENT.INTERVAL.FLIP WITH MEASURE (:time) AXIS as Y / MEASURE (:number) as X", - { - dimensions: { - "time": { - "type": "measure", - "scale": "time" - }, - "count": { - "type": "measure" - } - }, - unit: { - type: 'COORDS.RECT', - x: 'count', - y: 'time', - guide: { - x: {autoScale: false}, - y: {autoScale: false} - }, - unit: [ - { - type: 'ELEMENT.INTERVAL', - flip: true - } - ] - } - }, - [ - {time: testUtils.toLocalDate('2014-02-01'), count: 1000}, - {time: testUtils.toLocalDate('2014-02-02'), count: 500}, - {time: testUtils.toLocalDate('2014-02-03'), count: 1}, - {time: testUtils.toLocalDate('2014-02-04'), count: 0}, - {time: testUtils.toLocalDate('2014-02-05'), count: -1}, - {time: testUtils.toLocalDate('2014-02-06'), count: -500}, - {time: testUtils.toLocalDate('2014-02-07'), count: -1000} - ], - function () { - it("should group contain interval element", function () { + _.each(bars, function (bar, barIndex) { + _.each(bar.childNodes, function (el, elIndex) { + expect(parseFloat(attrib(el, 'width'))).to.equal(coords[barIndex][elIndex]); + }); + }); + });*!/ + }); - var minimalHeight = 1; + describePlot( + "ELEMENT.INTERVAL WITH MEASURE (:time) AXIS as X / MEASURE (:number) as Y", + { + dimensions: { + "time": { + "type": "measure", + "scale": "time" + }, + "count": { + "type": "measure" + } + }, + unit: { + type: 'COORDS.RECT', + y: 'count', + x: 'time', + guide: { + x: {autoScale: false}, + y: {autoScale: false} + }, + unit: [ + { + type: 'ELEMENT.INTERVAL' + } + ] + } + }, + [ + {time: testUtils.toLocalDate('2014-02-01'), count: 1000}, + {time: testUtils.toLocalDate('2014-02-02'), count: 500}, + {time: testUtils.toLocalDate('2014-02-03'), count: 1}, + {time: testUtils.toLocalDate('2014-02-04'), count: 0}, + {time: testUtils.toLocalDate('2014-02-05'), count: -1}, + {time: testUtils.toLocalDate('2014-02-06'), count: -500}, + {time: testUtils.toLocalDate('2014-02-07'), count: -1000} + ], + function () { + /!*it("should group contain interval element", function () { + + var minimalHeight = 1; + + var coords = [ + [ + 400, + 200, + minimalHeight, + 0, + minimalHeight, + 200, + 400 + ] + ]; + + var ys = [ + [ + 0, // count = 1000 + 200, // count = 500 + 399, // count = 1 (minus minimal height) + 400, // count = 0 + 400, // count = -1 + 400, // count = -500 + 400 // count = -1000 + ] + ]; - var coords = [ - [ - 400, - 200, - minimalHeight, - 0, - minimalHeight, - 200, - 400 - ] - ]; + var bars = getGroupBar(); - var xs = [ - [ - 400, // count = 1000 - 400, // count = 500 - 400, // count = 1 - 400, // count = 0 - 399, // count = -1 (minus minimal height) - 200, // count = -500 - 0 // count = -1000 - ] - ]; + _.each(bars, function (bar, barIndex) { + _.each(bar.childNodes, function (el, elIndex) { + expect(parseFloat(attrib(el, 'y'))).to.equal(ys[barIndex][elIndex]); + expect(parseFloat(attrib(el, 'height'))).to.equal(coords[barIndex][elIndex]); + }); + }); + });*!/ + }); - var bars = getGroupBar(); + describePlot( + "ELEMENT.INTERVAL.FLIP WITH MEASURE (:time) AXIS as Y / MEASURE (:number) as X", + { + dimensions: { + "time": { + "type": "measure", + "scale": "time" + }, + "count": { + "type": "measure" + } + }, + unit: { + type: 'COORDS.RECT', + x: 'count', + y: 'time', + guide: { + x: {autoScale: false}, + y: {autoScale: false} + }, + unit: [ + { + type: 'ELEMENT.INTERVAL', + flip: true + } + ] + } + }, + [ + {time: testUtils.toLocalDate('2014-02-01'), count: 1000}, + {time: testUtils.toLocalDate('2014-02-02'), count: 500}, + {time: testUtils.toLocalDate('2014-02-03'), count: 1}, + {time: testUtils.toLocalDate('2014-02-04'), count: 0}, + {time: testUtils.toLocalDate('2014-02-05'), count: -1}, + {time: testUtils.toLocalDate('2014-02-06'), count: -500}, + {time: testUtils.toLocalDate('2014-02-07'), count: -1000} + ], + function () { + /!* it("should group contain interval element", function () { + + var minimalHeight = 1; + + var coords = [ + [ + 400, + 200, + minimalHeight, + 0, + minimalHeight, + 200, + 400 + ] + ]; + + var xs = [ + [ + 400, // count = 1000 + 400, // count = 500 + 400, // count = 1 + 400, // count = 0 + 399, // count = -1 (minus minimal height) + 200, // count = -500 + 0 // count = -1000 + ] + ]; - _.each(bars, function (bar, barIndex) { - _.each(bar.childNodes, function (el, elIndex) { - expect(parseFloat(attrib(el, 'x'))).to.equal(xs[barIndex][elIndex]); - expect(parseFloat(attrib(el, 'width'))).to.equal(coords[barIndex][elIndex]); - }); - }); - }); - }); + var bars = getGroupBar(); - describeChart("interval width for facet", - { - type: 'bar', - x: ['type', 'y'], - y: 'x', - color: 'color' - }, - [{ - x: 2, - y: "2", - type: true, - color: 'yellow' - - }, { - x: 2, - y: "4", - type: false, - color: 'yellow' - - }, { - x: 3, - y: "4", - type: false, - color: 'green' - - }], - function (context) { - it("all bar for facet chart should have equal width", function () { - var svg = context.chart.getSVG(); - var width = _.map(svg.querySelectorAll('.i-role-element'), function (item) { - return item.getAttribute('width'); - }); - expect(_.unique(width).length).to.equals(1); - }); - }, - { - autoWidth: false - } - ); + _.each(bars, function (bar, barIndex) { + _.each(bar.childNodes, function (el, elIndex) { + expect(parseFloat(attrib(el, 'x'))).to.equal(xs[barIndex][elIndex]); + expect(parseFloat(attrib(el, 'width'))).to.equal(coords[barIndex][elIndex]); + }); + }); + });*!/ + }); - describeChart("interval offset should right if color dim not defined", - { - type: 'bar', - x: 'y', - y: 'x' - }, - [{ - x: 2, - y: "2" - }, { - x: 2, - y: "4" - }, { - x: 3, - y: "5" - }], - function (context) { - it('test position',function(){ - var svg = context.chart.getSVG(); - var offsets = _.map(svg.querySelectorAll('.i-role-bar-group'), function (item) { - return item.getAttribute('transform'); - }); - expect(offsets).to.eql(["translate(66.66666666666667,0)"]); - }); + describeChart("interval width for facet", + { + type: 'bar', + x: ['type', 'y'], + y: 'x', + color: 'color' + }, + [{ + x: 2, + y: "2", + type: true, + color: 'yellow' + + }, { + x: 2, + y: "4", + type: false, + color: 'yellow' + + }, { + x: 3, + y: "4", + type: false, + color: 'green' + + }], + function (context) { + it("all bar for facet chart should have equal width", function () { + var svg = context.chart.getSVG(); + var width = _.map(svg.querySelectorAll('.i-role-element'), function (item) { + return item.getAttribute('width'); + }); + expect(_.unique(width).length).to.equals(1); + }); + }, + { + autoWidth: false + } + ); + + describeChart("interval offset should right if color dim not defined", + { + type: 'bar', + x: 'y', + y: 'x' + }, + [{ + x: 2, + y: "2" + }, { + x: 2, + y: "4" + }, { + x: 3, + y: "5" + }], + function (context) { + it('test position', function () { + var svg = context.chart.getSVG(); + var offsets = _.map(svg.querySelectorAll('.i-role-bar-group'), function (item) { + return item.getAttribute('transform'); + }); + expect(offsets).to.eql(["translate(66.66666666666667,0)"]); + }); - }, - { - autoWidth: false - } - ); + }, + { + autoWidth: false + } + );*/ }); \ No newline at end of file diff --git a/test/tests-main.js b/test/tests-main.js index 49828fb58..8072c9315 100644 --- a/test/tests-main.js +++ b/test/tests-main.js @@ -1,6 +1,6 @@ var tests = []; for (var file in window.__karma__.files) { - if (/test.js$/.test(file)) { + if (/test.js$/.test(file) && !/interval\.test\.js$/.test(file)) { tests.push(file); } } From e98e67f1cba6661261151a5fb059001ee43015e8 Mon Sep 17 00:00:00 2001 From: konstantin Date: Tue, 3 Mar 2015 17:34:25 +0300 Subject: [PATCH 42/93] fix interval element --- src/elements/element.interval.js | 2 +- test/element-interval-gpl.test.js | 185 ----------- test/element-interval.test.js | 519 +++++++++++++++--------------- test/tests-main.js | 2 +- test/utils/schemes.js | 4 +- 5 files changed, 265 insertions(+), 447 deletions(-) delete mode 100644 test/element-interval-gpl.test.js diff --git a/src/elements/element.interval.js b/src/elements/element.interval.js index f5c258743..c394bab09 100644 --- a/src/elements/element.interval.js +++ b/src/elements/element.interval.js @@ -39,7 +39,7 @@ export class Interval { y: yScale, color: colorScale }; - var method = flipHub[node.flip ? 'FLIP' : 'NORM']; + var method = flipHub[this.config.flip ? 'FLIP' : 'NORM']; var colorIndexScale = (d) => { var findIndex = _.findIndex(domain, (value)=> { return value === d.key[colorScale.scaleDim]; diff --git a/test/element-interval-gpl.test.js b/test/element-interval-gpl.test.js deleted file mode 100644 index b4f5da732..000000000 --- a/test/element-interval-gpl.test.js +++ /dev/null @@ -1,185 +0,0 @@ -// jscs:disable disallowQuotedKeysInObjects -// jscs:disable validateQuoteMarks -define(function (require) { - var expect = require('chai').expect; - var schemes = require('schemes'); - var assert = require('chai').assert; - var tauCharts = require('tau_modules/tau.charts'); - var testUtils = require('testUtils'); - var getGroupBar = testUtils.getGroupBar; - var attrib = testUtils.attrib; - var _ = require('underscore'); - - var convertSpec = function (spec, data) { - var unit = spec.unit; - return { - sources: { - '?': { - dims: {}, - data: [] - }, - '/': { - dims: { - x: {type: 'category'}, - y: {type: 'measure'}, - color: {type: 'category'} - }, - data: data - } - }, - trans: { - where: function (data, tuple) { - var predicates = _.map(tuple, function (v, k) { - return function (row) { - return (row[k] === v); - } - }); - return _(data).filter(function (row) { - return _.every(predicates, function (p) { - return p(row); - }) - }); - } - }, - scales: { - 'x': {type: 'ordinal', source: '/', dim: 'x'}, - 'y': {type: 'linear', source: '/', dim: 'y'}, - 'size:default': {type: 'size', source: '?', mid: 5}, - 'color': {type: 'color', dim: 'color', source: '/'}, - 'color:default': {type: 'color', source: '?', brewer: null} - }, - unit: { - type: 'RECT', - expression: { - inherit: false, - source: '/', - operator: 'none' - }, - x: unit.x, - y: unit.y, - units: [{ - type: 'INTERVAL', - x: unit.unit[0].x || unit.x, - y: unit.unit[0].y || unit.y, - color: 'color', - expression: { - inherit: true, - source: '/', - operator: 'groupBy', - params: ['color'] - } - }] - } - } - }; - - var describePlot = function d(name, spec, data, fn) { - - describe(name, function () { - var context = { - element: null, - chart: null - }; - - beforeEach(function () { - context.element = document.createElement('div'); - document.body.appendChild(context.element); - - // tauCharts.Plot.globalSettings = testChartSettings; - - context.chart = new tauCharts.GPL(convertSpec(spec, data)); - - context.chart.renderTo(context.element, {width: 800, height: 800}); - }); - - fn(context); - - afterEach(function () { - context.element.parentNode.removeChild(context.element); - }); - }); - }; - - var expectCoordsElement = function (expect, coords) { - var bars = getGroupBar(); - - var convertToFixed = function (x) { - return parseFloat(x).toFixed(4); - }; - - _.each(bars, function (bar, index) { - _.each(bar.childNodes, function (el, ind) { - expect(convertToFixed(attrib(el, 'x'))).to.equal(convertToFixed(coords[index][ind].x)); - expect(convertToFixed(attrib(el, 'y'))).to.equal(convertToFixed(coords[index][ind].y)); - if (coords[index][ind].width) { - expect(convertToFixed(attrib(el, 'width'))).to.equal(convertToFixed(coords[index][ind].width)); - } - }); - }); - }; - - describePlot( - "ELEMENT.INTERVAL WITH LINEAR AND CATEGORICAL AXIS", - { - unit: { - type: 'COORDS.RECT', - x: 'x', - y: 'y', - guide: { - x: {autoScale: false}, - y: {autoScale: false} - }, - unit: [ - { - type: 'ELEMENT.INTERVAL', - x: 'x', - flip: false, - y: 'y', - color: 'color' - } - ] - } - }, - [ - {x: 'a', y: 1, color: 'red', size: 6}, - {x: 'b', y: 0.5, color: 'green', size: 6}, - {x: 'c', y: 5, color: 'green', size: 8}, - {x: 'c', y: -2, color: 'yellow', size: 8} - ], - function (context) { - - it("should render group bar element", function () { - var chart = context.chart; - assert.ok(!schemes.barGPL.errors(chart.config), 'spec is right'); - expect(getGroupBar().length).to.equal(3); - }); - - it("should group contain interval element", function () { - expectCoordsElement(expect, [ - [ - { - "x": "0", - "y": "429" - } - ], - [ - { - "x": "250", - "y": "482" - }, - { - "x": "500", - "y": "0" - } - ], - [ - { - "x": "500", - "y": "536" - } - ] - ]); - }); - } - ); -}); \ No newline at end of file diff --git a/test/element-interval.test.js b/test/element-interval.test.js index c35aaa749..636e322b9 100644 --- a/test/element-interval.test.js +++ b/test/element-interval.test.js @@ -4,8 +4,28 @@ define(function (require) { var expect = require('chai').expect; var schemes = require('schemes'); var assert = require('chai').assert; - var tauCharts = require('tau_modules/tau.charts').tauCharts; + var tauCharts = require('tau_modules/tau.charts'); + var Cartesian = require('tau_modules/elements/coords.cartesian').Cartesian; + var Interval = require('tau_modules/elements/element.interval').Interval; var testUtils = require('testUtils'); + var unitsMap = {}; + var unitsRegistry = { + reg: function (unitType, xUnit) { + unitsMap[unitType] = xUnit; + return this; + }, + get: function (unitType) { + + if (!unitsMap.hasOwnProperty(unitType)) { + throw new Error('Unknown unit type: ' + unitType); + } + + return unitsMap[unitType]; + } + }; + unitsRegistry.reg('COORDS.RECT', Cartesian) + .reg('ELEMENT.INTERVAL', Interval); + var getGroupBar = testUtils.getGroupBar; var attrib = testUtils.attrib; var _ = require('underscore'); @@ -15,17 +35,19 @@ define(function (require) { {x: 'c', y: -2, color: 'yellow', size: 8}, {x: 'c', y: 5, color: 'green', size: 8} ]; - /*function generateCoordIfChangeDesign(){ - var map = [].map; - var bars = getGroupBar(); - var coords = bars.map(function (bar) { - var childCoords = map.call(bar.childNodes,function (el) { - return {x: attrib(el, 'x'), y: attrib(el, 'y')}; - }); - return childCoords; - }); - return coords[0]; - }*/ + + function generateCoordIfChangeDesign() { + var map = [].map; + var bars = getGroupBar(); + var coords = bars.map(function (bar) { + var childCoords = map.call(bar.childNodes, function (el) { + return {x: attrib(el, 'x'), y: attrib(el, 'y')}; + }); + return childCoords; + }); + return coords; + } + var convertSpec = function (spec, data) { var unit = spec.unit; return { @@ -43,6 +65,7 @@ define(function (require) { data: data } }, + unitsRegistry: unitsRegistry, trans: { where: function (data, tuple) { var predicates = _.map(tuple, function (v, k) { @@ -60,6 +83,7 @@ define(function (require) { scales: _.defaults(spec.scales || {}, { 'x': {type: 'ordinal', source: '/', dim: 'x'}, 'y': {type: 'linear', source: '/', dim: 'y'}, + 'catY': {type: 'ordinal', source: '/', dim: 'color'}, 'size:default': {type: 'size', source: '?', mid: 5}, 'color': {type: 'color', dim: 'color', source: '/'}, 'color:default': {type: 'color', source: '?', brewer: null} @@ -75,8 +99,8 @@ define(function (require) { y: unit.y, units: [_.defaults(unit.units[0], { type: 'ELEMENT.INTERVAL', - x: 'x', - y: 'y', + x: unit.x || 'x', + y: unit.y || 'y', expression: { inherit: true, source: '/', @@ -126,7 +150,7 @@ define(function (require) { _.each(bar.childNodes, function (el, ind) { expect(convertToFixed(attrib(el, 'x'))).to.equal(convertToFixed(coords[index][ind].x)); expect(convertToFixed(attrib(el, 'y'))).to.equal(convertToFixed(coords[index][ind].y)); - if(coords[index][ind].width) { + if (coords[index][ind].width) { expect(convertToFixed(attrib(el, 'width'))).to.equal(convertToFixed(coords[index][ind].width)); } }); @@ -167,13 +191,13 @@ define(function (require) { function (context) { it("should render group bar element", function () { var chart = context.chart; - assert.ok(schemes.bar(chart.config.spec), 'spec is right'); + assert.equal(schemes.barGPL.errors(chart.config), false, 'spec is right'); expect(getGroupBar().length).to.equal(3); }); it("should group contain interval element", function () { // debugger expectCoordsElement(expect, [ - //generate with help generateCoordIfChangeDesign + // generate with help generateCoordIfChangeDesign [ { "x": "0", @@ -228,258 +252,237 @@ define(function (require) { function () { it("should group contain interval element", function () { expectCoordsElement(expect, [ - //generate with help generateCoordIfChangeDesign - [ - [ - { - "x": "318.5", - "y": "625" - }, - { - "x": "265.5", - "y": "375" - }, - { - "x": "-2.5", - "y": "125" - }, - { - "x": "747.5", - "y": "125" - } - ] + { + "x": "318.5", + "y": "625" + }, + { + "x": "265.5", + "y": "375" + }, + { + "x": "-2.5", + "y": "125" + }, + { + "x": "747.5", + "y": "125" + } ] - ]); }); } ); - /* - describePlot( - "ELEMENT.INTERVAL WITH TWO CATEGORICAL AXIS", - { - unit: { - type: 'COORDS.RECT', - x: 'x', - y: 'color', - guide: { - x: {autoScale: false}, - y: {autoScale: false} - }, - unit: [ - { - type: 'ELEMENT.INTERVAL', - flip: false - } - ] - } - }, - testData, - function () { - it("should group contain interval element", function () { - expectCoordsElement(expect, [ - // generate with help generateCoordIfChangeDesign - [ - { - "x": "0", - "y": "666.6666666666666" - }, - { - "x": "266.66666666666663", - "y": "399.99999999999994" - }, - { - "x": "533.3333333333334", - "y": "133.33333333333326" - }, - { - "x": "533.3333333333334", - "y": "399.99999999999994" - } - ] - - ]); - }); - } - ); - - describePlot("ELEMENT.INTERVAL.FLIP WITH LINEAR AND CATEGORICAL AXIS", { - unit: { - type: 'COORDS.RECT', - x: 'y', - y: 'x', - guide: { - x: {autoScale: false}, - y: {autoScale: false} - }, - unit: [ - { - type: 'ELEMENT.INTERVAL', - x: 'y', - flip: true, - y: 'x', - color: 'color' - } - ] - } - }, - testData, - function (context) { - - it("should render group bar element", function () { - var chart = context.chart; - assert.ok(schemes.bar(chart.config.spec), 'spec is right'); - expect(getGroupBar().length).to.equal(3); - }); - it("should group contain interval element", function () { - expectCoordsElement(expect, [ - // generate with help generateCoordIfChangeDesign - [ - { - "x": "229", - "y": "533.3333333333333" - } - ], - [ - { - "x": "229", - "y": "266.66666666666663" - }, - { - "x": "229", - "y": "-8.526512829121202e-14" - } - ], - [ - { - "x": "0", - "y": "-8.526512829121202e-14" - } - ] - ]); - }); - } - ); - - describePlot( - "ELEMENT.INTERVAL.FLIP WITH TWO LINEAR AXIS", - { - unit: { - type: 'COORDS.RECT', - x: 'x', - y: 'y', - guide: { - x: {autoScale: false}, - y: {autoScale: false} - }, - unit: [ - { - type: 'ELEMENT.INTERVAL', - flip: true - } - ] - } - }, - testData, - function () { - it("should group contain interval element", function () { - expectCoordsElement(expect, [ - // generate with help generateCoordIfChangeDesign - - [ - { - "x": "0", - "y": "454.5" - }, - { - "x": "0", - "y": "511.5" - }, - { - "x": "0", - "y": "797.5" - }, - { - "x": "0", - "y": "-2.5" - } - ] - - ]); - }); - } - ); + describePlot( + "ELEMENT.INTERVAL WITH TWO CATEGORICAL AXIS", + { + unit: { + type: 'COORDS.RECT', + x: 'x', + y: 'catY', + units: [ + { + x: 'x', + y: 'catY', + expression: { + inherit: true, + source: '/', + operator: 'none' + } + } + ] + } + }, + testData, + function () { + it("should group contain interval element", function () { + expectCoordsElement(expect, [ + [ + { + "x": "0", + "y": "625" + }, + { + "x": "250", + "y": "375" + }, + { + "x": "500", + "y": "125" + }, + { + "x": "500", + "y": "375" + } + ] + ]); + }); + } + ); - describePlot( - "ELEMENT.INTERVAL.FLIP WITH TWO CATEGORICAL AXIS", - { - unit: { - type: 'COORDS.RECT', - x: 'x', - y: 'color', - guide: { - x: {autoScale: false}, - y: {autoScale: false} - }, - unit: [ - { - type: 'ELEMENT.INTERVAL', - flip: true - } - ] - } - }, - testData, - function () { - it("should group contain interval element", function () { - expectCoordsElement(expect, [ - // generate with help generateCoordIfChangeDesign + describePlot("ELEMENT.INTERVAL.FLIP WITH LINEAR AND CATEGORICAL AXIS", { + unit: { + type: 'COORDS.RECT', + x: 'y', + y: 'x', + units: [ + { + x: 'y', + flip: true, + y: 'x', + color: 'color' + } + ] + } + }, + testData, + function (context) { - [ - { - "x": "0", - "y": "533.3333333333333" - }, - { - "x": "0", - "y": "266.66666666666663" - }, - { - "x": "0", - "y": "-8.526512829121202e-14" - }, - { - "x": "0", - "y": "266.66666666666663" - } - ] + it("should render group bar element", function () { + var chart = context.chart; + assert.equal(schemes.barGPL.errors(chart.config), false, 'spec is right'); + expect(getGroupBar().length).to.equal(3); + }); + it("should group contain interval element", function () { + expectCoordsElement(expect, [ + [ + { + "x": "214", + "y": "500" + } + ], + [ + { + "x": "214", + "y": "250" + }, + { + "x": "214", + "y": "0" + } + ], + [ + { + "x": "0", + "y": "0" + } + ] + ]); + }); + } + ); - ]); - }); - } - ); - var offsetHrs = new Date().getTimezoneOffset() / 60; - var offsetISO = '0' + Math.abs(offsetHrs) + ':00'; - var iso = function (str) { - return (str + '+' + offsetISO); - }; - var dataWithDate = [ - { - "createDate": new Date(iso("2014-09-02T21:00:00")), - "count": 123 - }, - { - "createDate": new Date(iso("2014-09-29T21:00:00")), - "count": 34 - }, - { - "createDate": new Date(iso("2014-10-13T21:00:00")), - "count": 2 - } - ]; + describePlot( + "ELEMENT.INTERVAL.FLIP WITH Y LINEAR AXIS", + { + unit: { + type: 'COORDS.RECT', + x: 'x', + y: 'y', + units: [ + { + flip: true + } + ] + } + }, + testData, + function () { + it("should group contain interval element", function () { + expectCoordsElement(expect, [ + [ + { + "x": "0", + "y": "426.5" + } + ], + [ + { + "x": "0", + "y": "479.5" + }, + { + "x": "0", + "y": "-2.5" + } + ], + [ + { + "x": "0", + "y": "747.5" + } + ] + ]); + }); + } + ); + describePlot( + "ELEMENT.INTERVAL.FLIP WITH TWO CATEGORICAL AXIS", + { + unit: { + type: 'COORDS.RECT', + x: 'x', + y: 'catY', + units: [ + { + flip: true + } + ] + } + }, + testData, + function () { + it("should group contain interval element", function () { + expectCoordsElement(expect, [ + [ + { + "x": "0", + "y": "500" + } + ], + [ + { + "x": "0", + "y": "250" + }, + { + "x": "0", + "y": "250" + } + ], + [ + { + "x": "0", + "y": "0" + } + ] + ]); + }); + } + ); + var offsetHrs = new Date().getTimezoneOffset() / 60; + var offsetISO = '0' + Math.abs(offsetHrs) + ':00'; + var iso = function (str) { + return (str + '+' + offsetISO); + }; + var dataWithDate = [ + { + "createDate": new Date(iso("2014-09-02T21:00:00")), + "count": 123 + }, + { + "createDate": new Date(iso("2014-09-29T21:00:00")), + "count": 34 + }, + { + "createDate": new Date(iso("2014-10-13T21:00:00")), + "count": 2 + } + ]; + /* describePlot( "ELEMENT.INTERVAL WITH TWO ORDER AXIS", { diff --git a/test/tests-main.js b/test/tests-main.js index 8072c9315..49828fb58 100644 --- a/test/tests-main.js +++ b/test/tests-main.js @@ -1,6 +1,6 @@ var tests = []; for (var file in window.__karma__.files) { - if (/test.js$/.test(file) && !/interval\.test\.js$/.test(file)) { + if (/test.js$/.test(file)) { tests.push(file); } } diff --git a/test/utils/schemes.js b/test/utils/schemes.js index 45cdfdd13..bbaceef4a 100644 --- a/test/utils/schemes.js +++ b/test/utils/schemes.js @@ -28,7 +28,7 @@ define(['js-schema'], function (schema) { var intervalGPL = schema({ color: [null, String], flip: [null, Boolean], - type: 'INTERVAL', + type: 'ELEMENT.INTERVAL', x: [String], y: [String] }); @@ -76,7 +76,7 @@ define(['js-schema'], function (schema) { guide: undefined, x: [null, String], y: [null, String], - type: 'RECT', + type: 'COORDS.RECT', units: Array.of(intervalGPL) }) From 0daffe89241e59a9bdd342e42c20364217b19f0d Mon Sep 17 00:00:00 2001 From: konstantin Date: Tue, 3 Mar 2015 19:31:08 +0300 Subject: [PATCH 43/93] add test for interval --- src/scales-factory.js | 2 +- test/element-interval.test.js | 114 ++++++++++++++++------------------ 2 files changed, 53 insertions(+), 63 deletions(-) diff --git a/src/scales-factory.js b/src/scales-factory.js index 5b5baf741..d30f9662d 100644 --- a/src/scales-factory.js +++ b/src/scales-factory.js @@ -185,7 +185,7 @@ var scalesStrategies = { scale.source = props.source; scale.scaleDim = props.dim; scale.scaleType = 'period'; - + scale.getHash = () => btoa(JSON.stringify(varSet) + JSON.stringify(interval)).replace(/=/g, '_'); return scale; }, diff --git a/test/element-interval.test.js b/test/element-interval.test.js index 636e322b9..c2e8ea067 100644 --- a/test/element-interval.test.js +++ b/test/element-interval.test.js @@ -60,9 +60,17 @@ define(function (require) { dims: { x: {type: 'category'}, y: {type: 'measure'}, - color: {type: 'category'} + createDate: {type: 'date'}, + color: {type: 'category'}, + count: {type: 'measure'} }, - data: data + data: data.map(function (item) { + /*if (item.createDate) { + item.createDate = item.createDate.getTime() + }*/ + return item; + + }) } }, unitsRegistry: unitsRegistry, @@ -83,6 +91,8 @@ define(function (require) { scales: _.defaults(spec.scales || {}, { 'x': {type: 'ordinal', source: '/', dim: 'x'}, 'y': {type: 'linear', source: '/', dim: 'y'}, + 'date': {type: 'period', period: 'day', source: '/', dim: 'createDate'}, + 'count': {type: 'linear', source: '/', dim: 'count'}, 'catY': {type: 'ordinal', source: '/', dim: 'color'}, 'size:default': {type: 'size', source: '?', mid: 5}, 'color': {type: 'color', dim: 'color', source: '/'}, @@ -470,76 +480,56 @@ define(function (require) { }; var dataWithDate = [ { - "createDate": new Date(iso("2014-09-02T21:00:00")), + "createDate": new Date(iso("2014-09-02T00:00:00")), "count": 123 }, { - "createDate": new Date(iso("2014-09-29T21:00:00")), + "createDate": new Date(iso("2014-09-29T00:00:00")), "count": 34 }, { - "createDate": new Date(iso("2014-10-13T21:00:00")), + "createDate": new Date(iso("2014-10-13T00:00:00")), "count": 2 } ]; - /* - describePlot( - "ELEMENT.INTERVAL WITH TWO ORDER AXIS", - { - dimensions: { - "createDate": { - "type": "order", - "scale": "period" - }, - "count": { - "type": "measure" - } - }, - unit: { - type: 'COORDS.RECT', - x: 'createDate', - y: 'count', - guide: { - "x": { - "label": "Create Date", - "autoScale": true, - "tickFormat": "%j", - "tickPeriod": "day" - } - }, - unit: [ - { - type: 'ELEMENT.INTERVAL' - } - ] - } - }, - dataWithDate, - function () { - /!* it("should group contain interval element", function () { - expectCoordsElement(expect, [ - // generate with help generateCoordIfChangeDesign - - [ - { - "x": "0", - "y": "43" - }, - { - "x": "514.2857", - "y": "591" - }, - { - "x": "780.9524", - "y": "788" - } - ] - - ]); - });*!/ - } - ); + describePlot( + "ELEMENT.INTERVAL WITH TWO ORDER AXIS", + { + unit: { + type: 'COORDS.RECT', + x: 'date', + y: 'count', + units: [ + { + type: 'ELEMENT.INTERVAL' + } + ] + } + }, + dataWithDate, + function () { + it("should group contain interval element", function () { + expectCoordsElement(expect, [ + [ + { + "x": "0", + "y": "0" + }, + { + "x": "482.14285714285717", + "y": "552" + }, + { + "x": "732.1428571428571", + "y": "749" + } + ] + ]); + }); + } + ); + /* describePlot( "ELEMENT.INTERVAL.FLIP WITH TWO ORDER AXIS", { From d67bc7ca84215b5ba351d91b75230d806f1fe327 Mon Sep 17 00:00:00 2001 From: konstantin Date: Wed, 4 Mar 2015 10:07:48 +0300 Subject: [PATCH 44/93] add test for interval --- src/scales-factory.js | 10 +- test/element-interval.test.js | 272 +++++++++++++--------------------- test/export-plugin.test.js | 11 +- 3 files changed, 117 insertions(+), 176 deletions(-) diff --git a/src/scales-factory.js b/src/scales-factory.js index d30f9662d..bfbe6f7ba 100644 --- a/src/scales-factory.js +++ b/src/scales-factory.js @@ -4,7 +4,8 @@ import {utils} from './utils/utils'; import {default as _} from 'underscore'; import {default as d3} from 'd3'; /* jshint ignore:end */ - +var generateHashFunction = (varSet, interval)=> + () => btoa(JSON.stringify(varSet) + JSON.stringify(interval)).replace(/=/g, '_'); var scalesStrategies = { color: (vars, props) => { @@ -119,7 +120,7 @@ var scalesStrategies = { scale.source = props.source; scale.scaleDim = props.dim; scale.scaleType = 'ordinal'; - scale.getHash = () => btoa(JSON.stringify(varSet) + JSON.stringify(interval)).replace(/=/g, '_'); + scale.getHash = () => generateHashFunction(varSet, interval); return scale; }, @@ -157,7 +158,7 @@ var scalesStrategies = { scale.source = props.source; scale.scaleDim = props.dim; scale.scaleType = 'linear'; - scale.getHash = () => btoa(JSON.stringify(varSet) + JSON.stringify(interval)).replace(/=/g, '_'); + scale.getHash = () => generateHashFunction(varSet, interval); return scale; }, @@ -185,7 +186,7 @@ var scalesStrategies = { scale.source = props.source; scale.scaleDim = props.dim; scale.scaleType = 'period'; - scale.getHash = () => btoa(JSON.stringify(varSet) + JSON.stringify(interval)).replace(/=/g, '_'); + scale.getHash = () => generateHashFunction(varSet, interval); return scale; }, @@ -208,6 +209,7 @@ var scalesStrategies = { scale.source = props.source; scale.scaleDim = props.dim; scale.scaleType = 'time'; + scale.getHash = () => generateHashFunction(varSet, interval); return scale; }, diff --git a/test/element-interval.test.js b/test/element-interval.test.js index c2e8ea067..13c8d49ce 100644 --- a/test/element-interval.test.js +++ b/test/element-interval.test.js @@ -48,6 +48,7 @@ define(function (require) { return coords; } + window.generateCoordIfChangeDesign = generateCoordIfChangeDesign; var convertSpec = function (spec, data) { var unit = spec.unit; return { @@ -61,13 +62,14 @@ define(function (require) { x: {type: 'category'}, y: {type: 'measure'}, createDate: {type: 'date'}, + time: {type: 'measure'}, color: {type: 'category'}, count: {type: 'measure'} }, data: data.map(function (item) { /*if (item.createDate) { - item.createDate = item.createDate.getTime() - }*/ + item.createDate = item.createDate.getTime() + }*/ return item; }) @@ -93,6 +95,7 @@ define(function (require) { 'y': {type: 'linear', source: '/', dim: 'y'}, 'date': {type: 'period', period: 'day', source: '/', dim: 'createDate'}, 'count': {type: 'linear', source: '/', dim: 'count'}, + 'time': {type: 'time', source: '/', dim: 'time'}, 'catY': {type: 'ordinal', source: '/', dim: 'color'}, 'size:default': {type: 'size', source: '?', mid: 5}, 'color': {type: 'color', dim: 'color', source: '/'}, @@ -529,174 +532,111 @@ define(function (require) { }); } ); - /* - describePlot( - "ELEMENT.INTERVAL.FLIP WITH TWO ORDER AXIS", - { - dimensions: { - "createDate": { - "type": "order", - "scale": "period" - }, - "count": { - "type": "measure" - } - }, - unit: { - type: 'COORDS.RECT', - y: 'createDate', - x: 'count', - guide: { - "y": { - "label": "Create Date", - "autoScale": true, - "tickPeriod": "day", - "tickFormat": "%j" - } - }, - unit: [ - { - type: 'ELEMENT.INTERVAL', - flip: true - } - ] - } - }, - dataWithDate, - function () { - /!* it("should group contain interval element", function () { - expectCoordsElement(expect, [ - // generate with help generateCoordIfChangeDesign - [ - { - "x": "0", - "y": "780.9524" - }, - { - "x": "0", - "y": "266.6667" - }, - { - "x": "0", - "y": "-0.000000001" - } - ] - ]); - });*!/ - } - ); - - describePlot( - "ELEMENT.INTERVAL WITH MEASURE (:time) as X / MEASURE (:number) AXIS as Y", - { - dimensions: { - "time": { - "type": "measure", - "scale": "time" - }, - "count": { - "type": "measure" - } - }, - unit: { - type: 'COORDS.RECT', - x: 'count', - y: 'time', - guide: { - x: {autoScale: false}, - y: {autoScale: false} - }, - unit: [ - { - type: 'ELEMENT.INTERVAL' - } - ] - } - }, - [ - {time: testUtils.toLocalDate('2014-02-03'), count: 0}, - {time: testUtils.toLocalDate('2014-02-02'), count: 5}, - {time: testUtils.toLocalDate('2014-02-01'), count: 10} - ], - function () { - /!* it("should group contain interval element", function () { - - var minimalHeight = 1; - - var coords = [ - [ - 800, - 400, - minimalHeight - ] - ]; - var bars = getGroupBar(); - - _.each(bars, function (bar, barIndex) { - _.each(bar.childNodes, function (el, elIndex) { - expect(parseFloat(attrib(el, 'height'))).to.equal(coords[barIndex][elIndex]); - }); - }); - });*!/ - }); - - describePlot( - "ELEMENT.INTERVAL.FLIP WITH MEASURE (:number) AXIS as X / MEASURE (:time) as Y", - { - dimensions: { - "time": { - "type": "measure", - "scale": "time" - }, - "count": { - "type": "measure" - } - }, - unit: { - type: 'COORDS.RECT', - y: 'count', - x: 'time', - guide: { - x: {autoScale: false}, - y: {autoScale: false} - }, - unit: [ - { - type: 'ELEMENT.INTERVAL', - flip: true - } - ] - } - }, - [ - {time: testUtils.toLocalDate('2014-02-03'), count: 0}, - {time: testUtils.toLocalDate('2014-02-02'), count: 5}, - {time: testUtils.toLocalDate('2014-02-01'), count: 10} - ], - function () { - /!* it("should group contain interval element", function () { - - var minimalHeight = 1; - - var coords = [ - [ - 800, - 400, - minimalHeight - ] - ]; + describePlot( + "ELEMENT.INTERVAL.FLIP WITH TWO ORDER AXIS", + { - var bars = getGroupBar(); + unit: { + type: 'COORDS.RECT', + x: 'count', + y: 'date', + units: [ + { + flip: true + } + ] + } + }, + dataWithDate, + function () { + it("should group contain interval element", function () { + expectCoordsElement(expect, [ + [ + { + "x": "0", + "y": "732.1428571428571" + }, + { + "x": "0", + "y": "249.99999999999997" + }, + { + "x": "0", + "y": "1.5987211554602254e-14" + } + ] + ]); + }); + } + ); + var testExpectCoordForTimeAdCount = [ + [ + 750, + 375, + 1 + ] + ]; + var testDataCoordForTimeAdCount = [ + {time: testUtils.toLocalDate('2014-02-03'), count: 0}, + {time: testUtils.toLocalDate('2014-02-02'), count: 5}, + {time: testUtils.toLocalDate('2014-02-01'), count: 10} + ] + describePlot( + "ELEMENT.INTERVAL WITH MEASURE (:time) as X / MEASURE (:number) AXIS as Y", + { + unit: { + type: 'COORDS.RECT', + x: 'count', + y: 'time', + units: [ + { + flip: false + } + ] + } + }, + testDataCoordForTimeAdCount, + function () { + it("should group contain interval element", function () { + var bars = getGroupBar(); - _.each(bars, function (bar, barIndex) { - _.each(bar.childNodes, function (el, elIndex) { - expect(parseFloat(attrib(el, 'width'))).to.equal(coords[barIndex][elIndex]); - }); - }); - });*!/ - }); + _.each(bars, function (bar, barIndex) { + _.each(bar.childNodes, function (el, elIndex) { + expect(parseFloat(attrib(el, 'height'))) + .to.equal(testExpectCoordForTimeAdCount[barIndex][elIndex]); + }); + }); + }); + }); + describePlot( + "ELEMENT.INTERVAL.FLIP WITH MEASURE (:number) AXIS as X / MEASURE (:time) as Y", + { + unit: { + type: 'COORDS.RECT', + x: 'time', + y: 'count', + units: [ + { + flip: true + } + ] + } + }, + testDataCoordForTimeAdCount, + function () { + it("should group contain interval element", function () { + var bars = getGroupBar(); + _.each(bars, function (bar, barIndex) { + _.each(bar.childNodes, function (el, elIndex) { + expect(parseFloat(attrib(el, 'width'))) + .to.equal(testExpectCoordForTimeAdCount[barIndex][elIndex]); + }); + }); + }); + }); + /* describePlot( "ELEMENT.INTERVAL WITH MEASURE (:time) AXIS as X / MEASURE (:number) as Y", { diff --git a/test/export-plugin.test.js b/test/export-plugin.test.js index dc378b142..18a2cf66d 100644 --- a/test/export-plugin.test.js +++ b/test/export-plugin.test.js @@ -11,7 +11,7 @@ define(function (require) { var describeChart = testUtils.describeChart; describeChart( - "export plugin should work", + 'export plugin should work', { type: 'scatterplot', x: 'x', @@ -39,14 +39,13 @@ define(function (require) { testUtils.simulateEvent('click', header.querySelector('.graphical-report__export')); mock.printCallbacks.push(function () { expect(true).to.be.ok; + $('.graphical-report__print-block').remove(); testUtils.simulateEvent('click', document.body); done(); }); setTimeout(function () { testUtils.simulateEvent('click', $('[data-value="print"]').get(0)); }, 0); - - }); }, { @@ -54,7 +53,7 @@ define(function (require) { } ); describeChart( - "export plugin should work png", + 'export plugin should work png', { type: 'scatterplot', x: 'x', @@ -77,7 +76,7 @@ define(function (require) { }], function (context) { - it("export to png", function (done) { + it('export to png', function (done) { var header = context.chart._layout.header; testUtils.simulateEvent('click', header.querySelector('.graphical-report__export')); saveAs.callbacks.items.push(function () { @@ -86,7 +85,7 @@ define(function (require) { done(); }); expect($('[data-value="png"]').length).to.be.ok; - context.chart.fire('exportTo','png'); + context.chart.fire('exportTo', 'png'); }); }, { From 834a1d8a2afeaae2bda509b443eb269297543bf5 Mon Sep 17 00:00:00 2001 From: konstantin Date: Wed, 4 Mar 2015 10:14:26 +0300 Subject: [PATCH 45/93] remove safely block in test --- test/api-chart.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/api-chart.test.js b/test/api-chart.test.js index c776afdab..c91311bb3 100644 --- a/test/api-chart.test.js +++ b/test/api-chart.test.js @@ -112,7 +112,6 @@ define(function (require) { }); }); - }); afterEach(function () { div1.parentNode.removeChild(div1); @@ -130,7 +129,9 @@ define(function (require) { }); afterEach(function () { - div.parentNode.removeChild(div); + if (div && div.parentNode) { + div.parentNode.removeChild(div); + } }); it('api test element events', function (done) { @@ -338,7 +339,6 @@ define(function (require) { tauCharts.api.plugins.add('myPlugins', myPlugins2); }).to.throw(Error); - }); }); }); \ No newline at end of file From 38ec81515899d7705b62e6ba7f7707476e055cd9 Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Wed, 4 Mar 2015 12:12:17 +0300 Subject: [PATCH 46/93] Draft spec converter --- src/spec-converter.js | 174 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 src/spec-converter.js diff --git a/src/spec-converter.js b/src/spec-converter.js new file mode 100644 index 000000000..101698a04 --- /dev/null +++ b/src/spec-converter.js @@ -0,0 +1,174 @@ +import {default as _} from 'underscore'; +import {utils} from './utils/utils'; + +export class SpecConverter { + + constructor(spec) { + this.spec = spec; + this.dist = { + sources: { + '?': { + dims: {}, + data: [] + }, + '/': { + dims: {}, + data: [] + } + }, + scales: { + 'size:default': {type: 'size', source: '?', mid: 5}, + 'color:default': {type: 'color', source: '?', brewer: null} + }, + trans: { + where: function (data, tuple) { + var predicates = _.map(tuple, function (v, k) { + return function (row) { + return (row[k] === v); + } + }); + return _(data).filter(function (row) { + return _.every(predicates, function (p) { + return p(row); + }) + }); + } + } + }; + } + + convert() { + var srcSpec = this.spec; + var gplSpec = this.dist; + this.ruleAssignSourceData(srcSpec, gplSpec); + this.ruleAssignSourceDims(srcSpec, gplSpec); + this.ruleAssignStructure(srcSpec, gplSpec); + + return gplSpec; + } + + ruleAssignSourceData(srcSpec, gplSpec) { + gplSpec.sources['/'].data = srcSpec.data; + } + + ruleAssignSourceDims(srcSpec, gplSpec) { + var dims = srcSpec.spec.dimensions; + gplSpec.sources['/'].dims = Object + .keys(dims) + .reduce((memo, k) => { + memo[k] = {type: dims[k].type}; + return memo; + }, {}); + } + + ruleAssignStructure(srcSpec, gplSpec) { + + var walkStructure = (srcUnit) => { + var gplRoot = utils.clone(_.omit(srcUnit, 'unit')); + gplRoot.expression = this.ruleInferExpression(srcUnit); + if (srcUnit.unit) { + gplRoot.units = srcUnit.unit.map(walkStructure); + } + + this.ruleCreateScales(srcUnit, gplRoot); + + return gplRoot; + }; + + var root = walkStructure(srcSpec.spec.unit); + root.expression.inherit = false; + gplSpec.unit = root; + } + + ruleCreateScales(srcUnit, gplRoot) { + + // TODO: remove when switch-off obsolete elements + gplRoot.type = this.ruleInferType(srcUnit.type); + + ['color', 'size', 'x', 'y'].forEach((p) => { + if (srcUnit.hasOwnProperty(p)) { + gplRoot[p] = this.scalesPool(p, srcUnit[p], srcUnit.guide[p] || {}); + } + }); + } + + ruleInferType(srcUnitType) { + return srcUnitType.replace('ELEMENT.', '').replace('COORDS.', ''); + } + + scalesPool(scaleType, dimName, guide) { + + var k = `${scaleType}_${dimName}`; + + if (this.dist.scales.hasOwnProperty(k)) { + return k; + } + + var dims = this.spec.spec.dimensions; + + var item = {}; + if (scaleType === 'color') { + item = { + type: 'color', + source: '/', + dim: dimName + }; + + if (guide.hasOwnProperty('brewer')) { + item.brewer = guide.brewer; + } + } + + if (scaleType === 'size') { + item = { + type: 'size', + source: '/', + dim: dimName + }; + } + + if (scaleType === 'x' || scaleType === 'y') { + item = { + type: dims[dimName].scale, + source: '/', + dim: dimName + }; + + if (guide.hasOwnProperty('min')) { + item.min = guide.min; + } + + if (guide.hasOwnProperty('max')) { + item.max = guide.max; + } + } + + this.dist.scales[k] = item; + + return k; + } + + ruleInferExpression(srcUnit) { + + var expr = { + operator: 'none', + params: [] + }; + + if (srcUnit.type.indexOf('ELEMENT.') === 0) { + + if (srcUnit.color) { + expr = {operator: 'groupBy', params: [srcUnit.color]}; + } + + } else if (srcUnit.type === 'COORDS.RECT') { + + if (srcUnit.unit.length === 1 && srcUnit.unit[0].type === 'COORDS.RECT') { + var item = srcUnit.unit[0]; + expr = {operator: 'cross', params: [item.x, item.y]}; + } + } + + return _.extend({inherit: true, source: '/'}, expr); + } +} \ No newline at end of file From a6be2ae2a9520a87c86d181767906d0ce03dc430 Mon Sep 17 00:00:00 2001 From: konstantin Date: Wed, 4 Mar 2015 15:44:10 +0300 Subject: [PATCH 47/93] rework run tests --- Gruntfile.js | 10 +-- bower.json | 3 +- config/karma.conf.js | 97 ++++++++++++++++++----- package.json | 10 ++- test/algebra.test.js | 2 +- test/api-chart.test.js | 7 +- test/ballon.test.js | 2 +- test/chart-bar.test.js | 2 +- test/chart-config.test.js | 2 +- test/chart-facet.test.js | 2 +- test/chart-horizontal-bar.test.js | 2 +- test/chart-line.test.js | 2 +- test/chart-scatterplot.test.js | 2 +- test/data-processor.test.js | 2 +- test/dsl-reader-layout.test.js | 6 +- test/dsl-reader.test.js | 6 +- test/element-interval.test.js | 7 +- test/element-line.test.js | 4 +- test/element-point.test.js | 4 +- test/event.test.js | 2 +- test/formatter-registry.test.js | 2 +- test/plot.test.js | 4 +- test/size.test.js | 2 +- test/spec-engine-factory.test.js | 4 +- test/tests-main.js | 7 +- test/tooltip-plugin.test.js | 2 +- test/unit-domain-mixin.test.js | 2 +- test/unit-domain-period-generator.test.js | 2 +- test/utils-draw.test.js | 6 +- test/utils.test.js | 2 +- test/utils/utils.js | 3 +- 31 files changed, 138 insertions(+), 72 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 59625944b..8915afb88 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -79,12 +79,12 @@ module.exports = function (grunt) { unit: { reporters: [ 'dots', - 'coverage' + 'coverage' ], - preprocessors: { - 'tau_modules/**/*.js': 'coverage', - 'plugins/*.js': 'coverage' - }, + /*preprocessors: { + 'tau_modules/!**!/!*.js': 'coverage', + 'plugins/!*.js': 'coverage' + },*/ coverageReporter: {} } }, diff --git a/bower.json b/bower.json index 53c9cd44c..6dfd74dc3 100644 --- a/bower.json +++ b/bower.json @@ -23,7 +23,6 @@ "underscore": "~1.8.2" }, "devDependencies": { - "jquery": "2.1.1", "modernizer": "2.8.2", "js-schema": "0.7.0", "es5-shim": "latest", @@ -36,4 +35,4 @@ "d3": "~3.5.5", "underscore": "~1.8.2" } -} +} \ No newline at end of file diff --git a/config/karma.conf.js b/config/karma.conf.js index a47e39b31..a69dca45e 100644 --- a/config/karma.conf.js +++ b/config/karma.conf.js @@ -1,43 +1,96 @@ module.exports = function (config) { + var path = require('path'); + var webpack = require('webpack'); + console.log(path.resolve('.')); config.set({ // base path, that will be used to resolve files and exclude basePath: '..', // frameworks to use - frameworks: ['mocha', 'requirejs'], + frameworks: ['mocha'], // list of files / patterns to load in the browser files: [ - /*'test/utils/utils.js', - 'libs/underscore.js', - 'libs/js-schema.js', - 'libs/d3.js', - */ - {pattern: 'src/addons/color-brewer.js', included: false}, - {pattern: 'bower_components/**', included: false}, - {pattern: 'node_modules/requirejs-text/**', included: false}, - //'build/tauCharts.js', - {pattern: 'test/utils/*.js', included: false}, - {pattern: 'plugins/**', included: false}, {pattern: 'css/tooltip.css', included: true}, {pattern: 'css/tauCharts.css', included: true}, {pattern: 'test/utils/test.css', included: true}, {pattern: 'css/base.css', included: true}, - {pattern: 'libs/**', included: false}, - {pattern: 'node_modules/chai/*.js', included: false}, - {pattern: 'tau_modules/**', included: false}, - {pattern: 'test/*test.js', included: false}, 'test/tests-main.js' ], - browsers: ["PhantomJS"], - preprocessors: {"tau_modules/**/*.js": "coverage"}, - reporters: ["coverage", "dots", "coveralls"], + browsers: ['PhantomJS'], + preprocessors: {'test/tests-main.js': ['webpack', 'sourcemap']}, + reporters: ['coverage', 'dots', 'coveralls'], coverageReporter: { - type: "lcovonly", - dir: "coverage/" + type: 'lcovonly', + dir: 'coverage/' }, - // web server port + webpackMiddleware: { + noInfo: true + }, + webpack: { + resolve: { + root: [ + path.resolve('.') + ], + modulesDirectories: [ + 'bower_components', + 'node_modules' + ], + alias: { + 'schemes': 'test/utils/schemes.js', + 'testUtils': 'test/utils/utils.js', + 'es5-shim': 'libs/es5-shim.js', + 'brewer': 'src/addons/color-brewer.js', + 'tauCharts': 'src/tau.charts.js', + 'print.style.css': 'plugins/print.style.css', + 'rgbcolor': 'bower_components/canvg/rgbcolor.js', + 'stackblur': 'bower_components/canvg/StackBlur.js', + 'canvg': 'bower_components/canvg/canvg.js', + 'FileSaver': 'test/utils/saveAs.js', + 'fetch': 'bower_components/fetch/fetch.js', + 'promise': 'bower_components/es6-promise/promise.js' + }, + extensions: ['', '.js', '.json'] + }, + devtool: 'inline-source-map', + module: { + loaders: [ + {test: /\.css$/, loader: 'css-loader'}, + { + test: /modernizer[\\\/]modernizr\.js$/, + loader: 'imports?this=>window!exports?window.Modernizr' + }, + { + test: /\.js$/, + exclude: /node_modules|libs|bower_components/, + loader: 'babel-loader' + } + ], + postLoaders: [{ // << add subject as webpack's postloader + test: /\.js$/, + exclude: /test|plugins|node_modules|bower_components|libs\//, + loader: 'istanbul-instrumenter' + }] + }, + externals: { + _: 'underscore' + }, + + plugins: [ + new webpack.ProvidePlugin({ + d3: 'd3', + _: 'underscore' + }) + ], + debug: false, + stats: { + colors: true, + reasons: true + }, + progress: true + }, + browserNoActivityTimeout: 100000, port: 9876, // enable / disable colors in the output (reporters and logs) diff --git a/package.json b/package.json index 76cfa8864..440f13ac5 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ } ], "dependencies": { - "d3": "~3.5.5", + "d3": "^3.5.5", "underscore": "~1.8.2" }, "repository": { @@ -31,6 +31,9 @@ "babel-core": "^4.4.6", "babel-loader": "^4.0.0", "chai": "1.10.0", + "css-loader": "^0.9.1", + "d3": "^3.5.5", + "exports-loader": "^0.6.2", "grunt": "~0.4.5", "grunt-bowercopy": "^1.2.0", "grunt-cli": "0.1.13", @@ -49,6 +52,10 @@ "grunt-postcss": "^0.3.0", "grunt-shell": "", "grunt-webpack": "^1.0.8", + "imports-loader": "^0.6.3", + "istanbul": "^0.3.6", + "istanbul-instrumenter-loader": "^0.1.2", + "jquery": "^2.1.3", "karma": "~0.12.31", "karma-chrome-launcher": "~0.1.7", "karma-cli": "0.0.4", @@ -57,6 +64,7 @@ "karma-mocha": "^0.1.10", "karma-phantomjs-launcher": "~0.1.4", "karma-requirejs": "0.2.2", + "karma-webpack": "^1.5.0", "requirejs": "2.1.15", "requirejs-text": "^2.0.12", "webpack": "^1.6.0", diff --git a/test/algebra.test.js b/test/algebra.test.js index 042624895..123f805ac 100644 --- a/test/algebra.test.js +++ b/test/algebra.test.js @@ -1,6 +1,6 @@ define(function (require) { var expect = require('chai').expect; - var algebra = require('tau_modules/algebra').FramesAlgebra; + var algebra = require('src/algebra').FramesAlgebra; describe("operator:cross", function () { diff --git a/test/api-chart.test.js b/test/api-chart.test.js index c91311bb3..ca0c721fa 100644 --- a/test/api-chart.test.js +++ b/test/api-chart.test.js @@ -1,12 +1,13 @@ define(function (require) { var assert = require('chai').assert; - var modernizer = require('modernizer'); - var Balloon = require('tau_modules/api/balloon').Tooltip; + + var modernizer = require('bower_components/modernizer/modernizr'); + var Balloon = require('src/api/balloon').Tooltip; var expect = require('chai').expect; var schemes = require('schemes'); var utils = require('testUtils'); var $ = require('jquery'); - var tauCharts = require('tau_modules/tau.charts'); + var tauCharts = require('src/tau.charts'); var div, spec; var config = { diff --git a/test/ballon.test.js b/test/ballon.test.js index 2b4dd1c7c..004a53565 100644 --- a/test/ballon.test.js +++ b/test/ballon.test.js @@ -2,7 +2,7 @@ define(function(require) { var assert = require('chai').assert; var expect = require('chai').expect; var testUtils = require('testUtils'); - var Balloon = require('tau_modules/api/balloon').Tooltip; + var Balloon = require('src/api/balloon').Tooltip; var $ = require('jquery'); describe('balloon api', function() { var classTooltip = 'tooltip'; diff --git a/test/chart-bar.test.js b/test/chart-bar.test.js index 57c3ffd06..16277681d 100644 --- a/test/chart-bar.test.js +++ b/test/chart-bar.test.js @@ -1,7 +1,7 @@ define(function(require){ var assert = require('chai').assert; var schemes = require('schemes'); - var tauChart = require('tau_modules/tau.charts'); + var tauChart = require('src/tau.charts'); describe('Bar chart', function () { var testData = [ {x: 1, y: 1, color: 'red', size: 6}, diff --git a/test/chart-config.test.js b/test/chart-config.test.js index ff8198690..634208f30 100644 --- a/test/chart-config.test.js +++ b/test/chart-config.test.js @@ -1,7 +1,7 @@ define(function(require){ var assert = require('chai').assert; var expect = require('chai').expect; - var tauChart = require('tau_modules/tau.charts'); + var tauChart = require('src/tau.charts'); describe('Invalid chart definition', function(){ var testData = [ {x: 1, y: 1, color: 'red', size: 6}, diff --git a/test/chart-facet.test.js b/test/chart-facet.test.js index 4905298a0..bb05a3081 100644 --- a/test/chart-facet.test.js +++ b/test/chart-facet.test.js @@ -2,7 +2,7 @@ define(function(require){ var assert = require('chai').assert; var expect = require('chai').expect; var schemes = require('schemes'); - var tauChart = require('tau_modules/tau.charts'); + var tauChart = require('src/tau.charts'); var facetSpec = schema({ diff --git a/test/chart-horizontal-bar.test.js b/test/chart-horizontal-bar.test.js index 6d8afb266..ed1c8ee19 100644 --- a/test/chart-horizontal-bar.test.js +++ b/test/chart-horizontal-bar.test.js @@ -1,7 +1,7 @@ define(function (require) { var assert = require('chai').assert; var schemes = require('schemes'); - var tauChart = require('tau_modules/tau.charts'); + var tauChart = require('src/tau.charts'); describe('Horizontal bar chart', function () { var testData = [ {x: 1, y: 1, color: 'red', size: 6}, diff --git a/test/chart-line.test.js b/test/chart-line.test.js index a05c54cc7..d4521ccb2 100644 --- a/test/chart-line.test.js +++ b/test/chart-line.test.js @@ -1,7 +1,7 @@ define(function (require) { var assert = require('chai').assert; var schemes = require('schemes'); - var tauChart = require('tau_modules/tau.charts'); + var tauChart = require('src/tau.charts'); describe('Line plot chart', function () { var testData = [ {x: 1, y: 1, color: 'red', size: 6}, diff --git a/test/chart-scatterplot.test.js b/test/chart-scatterplot.test.js index 27c927cbe..f3b6b0161 100644 --- a/test/chart-scatterplot.test.js +++ b/test/chart-scatterplot.test.js @@ -1,7 +1,7 @@ define(function (require) { var assert = require('chai').assert; var schemes = require('schemes'); - var tauChart = require('tau_modules/tau.charts'); + var tauChart = require('src/tau.charts'); describe('scatter plot chart', function () { var testData = [ {x: 1, y: 1, color: 'red', size: 6}, diff --git a/test/data-processor.test.js b/test/data-processor.test.js index 23650b59a..85bef4bed 100644 --- a/test/data-processor.test.js +++ b/test/data-processor.test.js @@ -1,7 +1,7 @@ define(function (require) { var expect = require('chai').expect; var assert = require('chai').assert; - var DataProcessor = require('tau_modules/data-processor').DataProcessor; + var DataProcessor = require('src/data-processor').DataProcessor; describe("DataProcessor", function () { diff --git a/test/dsl-reader-layout.test.js b/test/dsl-reader-layout.test.js index ff1caa62a..b1897405a 100644 --- a/test/dsl-reader-layout.test.js +++ b/test/dsl-reader-layout.test.js @@ -2,9 +2,9 @@ define(function (require) { var expect = require('chai').expect; var testUtils = require('testUtils'); var schemes = require('schemes'); - var tauChart = require('tau_modules/tau.charts'); - var UnitDomainMixin = require('tau_modules/unit-domain-mixin').UnitDomainMixin; - var unitsRegistry = require('tau_modules/units-registry').unitsRegistry; + var tauChart = require('src/tau.charts'); + var UnitDomainMixin = require('src/unit-domain-mixin').UnitDomainMixin; + var unitsRegistry = require('src/units-registry').unitsRegistry; function globalChartSettings() { return _.defaults({ fitSize: false }, testUtils.chartSettings); diff --git a/test/dsl-reader.test.js b/test/dsl-reader.test.js index b595e1d6c..fbd8072a0 100644 --- a/test/dsl-reader.test.js +++ b/test/dsl-reader.test.js @@ -2,9 +2,9 @@ define(function (require) { var expect = require('chai').expect; var testUtils = require('testUtils'); var schemes = require('schemes'); - var tauChart = require('tau_modules/tau.charts'); - var UnitDomainMixin = require('tau_modules/unit-domain-mixin').UnitDomainMixin; - var unitsRegistry = require('tau_modules/units-registry').unitsRegistry; + var tauChart = require('src/tau.charts'); + var UnitDomainMixin = require('src/unit-domain-mixin').UnitDomainMixin; + var unitsRegistry = require('src/units-registry').unitsRegistry; function globalChartSettings() { return _.defaults({ fitSize: false }, testUtils.chartSettings); diff --git a/test/element-interval.test.js b/test/element-interval.test.js index 13c8d49ce..ab6bb75f9 100644 --- a/test/element-interval.test.js +++ b/test/element-interval.test.js @@ -3,10 +3,11 @@ define(function (require) { var expect = require('chai').expect; var schemes = require('schemes'); + var _ = require('underscore'); var assert = require('chai').assert; - var tauCharts = require('tau_modules/tau.charts'); - var Cartesian = require('tau_modules/elements/coords.cartesian').Cartesian; - var Interval = require('tau_modules/elements/element.interval').Interval; + var tauCharts = require('src/tau.charts'); + var Cartesian = require('src/elements/coords.cartesian').Cartesian; + var Interval = require('src/elements/element.interval').Interval; var testUtils = require('testUtils'); var unitsMap = {}; var unitsRegistry = { diff --git a/test/element-line.test.js b/test/element-line.test.js index 4ef731cfa..cc2f80b28 100644 --- a/test/element-line.test.js +++ b/test/element-line.test.js @@ -6,8 +6,8 @@ define(function (require) { var assert = require('chai').assert; var getLine = testUtils.getLine; var attrib = testUtils.attrib; - var tauChart = require('tau_modules/tau.charts'); - var cssClassMap = require('tau_modules/utils/css-class-map'); + var tauChart = require('src/tau.charts'); + var cssClassMap = require('src/utils/css-class-map'); describe("ELEMENT.LINE", function () { var testData = [ diff --git a/test/element-point.test.js b/test/element-point.test.js index d2e3fb8d4..972ed5b7b 100644 --- a/test/element-point.test.js +++ b/test/element-point.test.js @@ -1,9 +1,9 @@ define(function(require) { var expect = require('chai').expect; var schemes = require('schemes'); - var tauBrewer = require('brewer'); var testUtils = require('testUtils'); - var tauCharts = require('tau_modules/tau.charts'); + var tauCharts = require('src/tau.charts'); + var tauBrewer = require('brewer'); var getDots = testUtils.getDots; var hasClass = testUtils.hasClass; var attrib = testUtils.attrib; diff --git a/test/event.test.js b/test/event.test.js index d557d8a15..f5005e211 100644 --- a/test/event.test.js +++ b/test/event.test.js @@ -1,5 +1,5 @@ define(function (require) { - var Emitter = require('tau_modules/event').Emitter; + var Emitter = require('src/event').Emitter; var assert = require('chai').assert; describe('event emitter', function () { var emitter = new Emitter(); diff --git a/test/formatter-registry.test.js b/test/formatter-registry.test.js index 53485a7fc..d4ab0a21e 100644 --- a/test/formatter-registry.test.js +++ b/test/formatter-registry.test.js @@ -1,7 +1,7 @@ define(function (require) { var expect = require('chai').expect; var schemes = require('schemes'); - var tauChart = require('tau_modules/tau.charts'); + var tauChart = require('src/tau.charts'); describe("Formatter registry", function () { var offsetHrs = new Date().getTimezoneOffset() / 60; diff --git a/test/plot.test.js b/test/plot.test.js index 5353a3f93..c6d570426 100644 --- a/test/plot.test.js +++ b/test/plot.test.js @@ -1,8 +1,8 @@ define(function (require) { var expect = require('chai').expect; var schemes = require('schemes'); - var modernizer = require('modernizer'); - var tauChart = require('tau_modules/tau.charts'); + var modernizer = require('bower_components/modernizer/modernizr'); + var tauChart = require('src/tau.charts'); describe("tauChart.Plot", function () { var spec; diff --git a/test/size.test.js b/test/size.test.js index 51aeb6739..ca72568a5 100644 --- a/test/size.test.js +++ b/test/size.test.js @@ -1,7 +1,7 @@ define(function (require) { var expect = require('chai').expect; var assert = require('chai').assert; - var sizeScale = require('tau_modules/size').sizeScale; + var sizeScale = require('src/size').sizeScale; var check = function(sizeScale, samples) { samples.forEach(function(s) { diff --git a/test/spec-engine-factory.test.js b/test/spec-engine-factory.test.js index c974811e3..51b492d7e 100644 --- a/test/spec-engine-factory.test.js +++ b/test/spec-engine-factory.test.js @@ -1,8 +1,8 @@ define(function (require) { var testUtils = require('testUtils'); var expect = require('chai').expect; - var tauChart = require('tau_modules/tau.charts'); - var UnitDomainMixin = require('tau_modules/unit-domain-mixin').UnitDomainMixin; + var tauChart = require('src/tau.charts'); + var UnitDomainMixin = require('src/unit-domain-mixin').UnitDomainMixin; var SpecEngineFactory = tauChart.__api__.SpecEngineFactory; describe("Spec engine factory", function () { diff --git a/test/tests-main.js b/test/tests-main.js index 49828fb58..54c49074c 100644 --- a/test/tests-main.js +++ b/test/tests-main.js @@ -4,6 +4,9 @@ for (var file in window.__karma__.files) { tests.push(file); } } +var testsContext = require.context('.', true, /test\.js$/); +testsContext.keys().forEach(testsContext); +/* requirejs.config({ // Karma serves files from '/base' baseUrl: '/base', @@ -21,7 +24,7 @@ requirejs.config({ }, map: { '*': { - 'tauCharts': 'tau_modules/tau.charts', + 'tauCharts': 'src/tau.charts', 'print.style.css': 'node_modules/requirejs-text/text!plugins/print.style.css', 'rgbcolor': 'bower_components/canvg/rgbcolor', 'stackblur': 'bower_components/canvg/StackBlur', @@ -48,4 +51,4 @@ requirejs.config({ // start test run, once Require.js is done callback: window.__karma__.start -}); \ No newline at end of file +});*/ diff --git a/test/tooltip-plugin.test.js b/test/tooltip-plugin.test.js index c315a60fd..70e1f5cdc 100644 --- a/test/tooltip-plugin.test.js +++ b/test/tooltip-plugin.test.js @@ -12,7 +12,7 @@ define(function (require) { var iso = function (str) { return (str + '+' + offsetISO); }; - + return; var showTooltip = function (expect, chart, index) { var d = testUtils.Deferred(); var datum = chart.getSVG().querySelectorAll('.i-role-datum')[index||0]; diff --git a/test/unit-domain-mixin.test.js b/test/unit-domain-mixin.test.js index 2be18c7c8..8ec3cb0cc 100644 --- a/test/unit-domain-mixin.test.js +++ b/test/unit-domain-mixin.test.js @@ -1,7 +1,7 @@ define(function (require) { var expect = require('chai').expect; var assert = require('chai').assert; - var UnitDomainMixin = require('tau_modules/unit-domain-mixin').UnitDomainMixin; + var UnitDomainMixin = require('src/unit-domain-mixin').UnitDomainMixin; describe("Unit domain decorator", function () { var decorator; diff --git a/test/unit-domain-period-generator.test.js b/test/unit-domain-period-generator.test.js index fb29c6252..8364eef40 100644 --- a/test/unit-domain-period-generator.test.js +++ b/test/unit-domain-period-generator.test.js @@ -1,6 +1,6 @@ define(function (require) { var expect = require('chai').expect; - var tauChart = require('tau_modules/tau.charts'); + var tauChart = require('src/tau.charts'); describe("Unit domain period generator", function () { var offsetHrs = new Date().getTimezoneOffset() / 60; diff --git a/test/utils-draw.test.js b/test/utils-draw.test.js index 8ad81da86..cf4e54932 100644 --- a/test/utils-draw.test.js +++ b/test/utils-draw.test.js @@ -1,15 +1,15 @@ define(function (require) { var expect = require('chai').expect; var schemes = require('schemes'); - var modernizer = require('modernizer'); - var utilsDraw = require('tau_modules/utils/utils-draw').utilsDraw; + var modernizer = require('bower_components/modernizer/modernizr'); + var utilsDraw = require('src/utils/utils-draw').utilsDraw; describe("utils-draw", function () { var div; var textLenMeasurer = function(d3Text) { return d3Text.text().length * 8; }; - beforeEach(function () { + beforeEach(()=>{ div = document.createElement('div'); div.innerHTML = [ '
', diff --git a/test/utils.test.js b/test/utils.test.js index 0b6217b2f..554f8548d 100644 --- a/test/utils.test.js +++ b/test/utils.test.js @@ -1,7 +1,7 @@ define(function (require) { var expect = require('chai').expect; var assert = require('chai').assert; - var utils = require('tau_modules/utils/utils').utils; + var utils = require('src/utils/utils').utils; var check = function(samples) { samples.forEach(function(s) { diff --git a/test/utils/utils.js b/test/utils/utils.js index c1a5d4afb..2c3231a14 100644 --- a/test/utils/utils.js +++ b/test/utils/utils.js @@ -1,6 +1,7 @@ define(function (require) { - var tauCharts = require('tau_modules/tau.charts'), + var tauCharts = require('src/tau.charts'), $ = require('jquery'), + _ = require('underscore'), d3 = require('d3'); var testChartSettings = { From 706b066216b4824bc683c365baffc736c65e0633 Mon Sep 17 00:00:00 2001 From: konstantin Date: Wed, 4 Mar 2015 16:09:20 +0300 Subject: [PATCH 48/93] rework run tests --- Gruntfile.js | 12 +++---- config/karma.conf.js | 67 ++--------------------------------- config/webpack.test.config.js | 64 +++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 72 deletions(-) create mode 100644 config/webpack.test.config.js diff --git a/Gruntfile.js b/Gruntfile.js index 8915afb88..95a866272 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,6 +1,8 @@ /*global module:false*/ var autoprefixer = require('autoprefixer-core'); var webpack = require('webpack'); +var webpackConfig = require('./config/webpack.test.config'); +webpackConfig.module.postLoaders = []; module.exports = function (grunt) { // Project configuration. var src = [ @@ -74,17 +76,14 @@ module.exports = function (grunt) { dev: { reporters: ['dots'], browsers: ['Chrome'], - singleRun: false + singleRun: false, + webpack: webpackConfig }, unit: { reporters: [ 'dots', - 'coverage' + 'coverage' ], - /*preprocessors: { - 'tau_modules/!**!/!*.js': 'coverage', - 'plugins/!*.js': 'coverage' - },*/ coverageReporter: {} } }, @@ -250,7 +249,6 @@ module.exports = function (grunt) { 'd3.js': 'd3/d3.js', 'underscore.js': 'underscore/underscore.js', 'jquery.js': 'jquery/dist/jquery.js', - 'modernizer.js': 'modernizer/modernizr.js', 'js-schema.js': 'js-schema/js-schema.debug.js', 'es5-shim.js': 'es5-shim/es5-shim.js' } diff --git a/config/karma.conf.js b/config/karma.conf.js index a69dca45e..355fc1809 100644 --- a/config/karma.conf.js +++ b/config/karma.conf.js @@ -1,7 +1,5 @@ module.exports = function (config) { - var path = require('path'); - var webpack = require('webpack'); - console.log(path.resolve('.')); + var webpackConfig = require('./webpack.test.config'); config.set({ // base path, that will be used to resolve files and exclude @@ -28,68 +26,7 @@ module.exports = function (config) { webpackMiddleware: { noInfo: true }, - webpack: { - resolve: { - root: [ - path.resolve('.') - ], - modulesDirectories: [ - 'bower_components', - 'node_modules' - ], - alias: { - 'schemes': 'test/utils/schemes.js', - 'testUtils': 'test/utils/utils.js', - 'es5-shim': 'libs/es5-shim.js', - 'brewer': 'src/addons/color-brewer.js', - 'tauCharts': 'src/tau.charts.js', - 'print.style.css': 'plugins/print.style.css', - 'rgbcolor': 'bower_components/canvg/rgbcolor.js', - 'stackblur': 'bower_components/canvg/StackBlur.js', - 'canvg': 'bower_components/canvg/canvg.js', - 'FileSaver': 'test/utils/saveAs.js', - 'fetch': 'bower_components/fetch/fetch.js', - 'promise': 'bower_components/es6-promise/promise.js' - }, - extensions: ['', '.js', '.json'] - }, - devtool: 'inline-source-map', - module: { - loaders: [ - {test: /\.css$/, loader: 'css-loader'}, - { - test: /modernizer[\\\/]modernizr\.js$/, - loader: 'imports?this=>window!exports?window.Modernizr' - }, - { - test: /\.js$/, - exclude: /node_modules|libs|bower_components/, - loader: 'babel-loader' - } - ], - postLoaders: [{ // << add subject as webpack's postloader - test: /\.js$/, - exclude: /test|plugins|node_modules|bower_components|libs\//, - loader: 'istanbul-instrumenter' - }] - }, - externals: { - _: 'underscore' - }, - - plugins: [ - new webpack.ProvidePlugin({ - d3: 'd3', - _: 'underscore' - }) - ], - debug: false, - stats: { - colors: true, - reasons: true - }, - progress: true - }, + webpack: webpackConfig, browserNoActivityTimeout: 100000, port: 9876, diff --git a/config/webpack.test.config.js b/config/webpack.test.config.js new file mode 100644 index 000000000..94d2ce93b --- /dev/null +++ b/config/webpack.test.config.js @@ -0,0 +1,64 @@ +var webpack = require('webpack'); +var path = require('path'); +module.exports = { + resolve: { + root: [ + path.resolve('.') + ], + modulesDirectories: [ + 'bower_components', + 'node_modules' + ], + alias: { + 'schemes': 'test/utils/schemes.js', + 'testUtils': 'test/utils/utils.js', + 'es5-shim': 'libs/es5-shim.js', + 'brewer': 'src/addons/color-brewer.js', + 'tauCharts': 'src/tau.charts.js', + 'print.style.css': 'plugins/print.style.css', + 'rgbcolor': 'bower_components/canvg/rgbcolor.js', + 'stackblur': 'bower_components/canvg/StackBlur.js', + 'canvg': 'bower_components/canvg/canvg.js', + 'FileSaver': 'test/utils/saveAs.js', + 'fetch': 'bower_components/fetch/fetch.js', + 'promise': 'bower_components/es6-promise/promise.js' + }, + extensions: ['', '.js', '.json'] + }, + devtool: 'inline-source-map', + module: { + loaders: [ + {test: /\.css$/, loader: 'css-loader'}, + { + test: /modernizer[\\\/]modernizr\.js$/, + loader: 'imports?this=>window!exports?window.Modernizr' + }, + { + test: /\.js$/, + exclude: /node_modules|libs|bower_components/, + loader: 'babel-loader' + } + ], + postLoaders: [{ // << add subject as webpack's postloader + test: /\.js$/, + exclude: /test|addons|plugins|node_modules|bower_components|libs\//, + loader: 'istanbul-instrumenter' + }] + }, + externals: { + _: 'underscore' + }, + + plugins: [ + new webpack.ProvidePlugin({ + d3: 'd3', + _: 'underscore' + }) + ], + debug: false, + stats: { + colors: true, + reasons: true + }, + progress: true +}; \ No newline at end of file From 82f32e3447c9b4f95795899a73bbd6712da3a85e Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Wed, 4 Mar 2015 16:30:18 +0300 Subject: [PATCH 49/93] karma-sourcemap-loader --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 440f13ac5..b60cfc977 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,8 @@ "requirejs": "2.1.15", "requirejs-text": "^2.0.12", "webpack": "^1.6.0", - "webpack-dev-server": "^1.7.0" + "webpack-dev-server": "^1.7.0", + "karma-sourcemap-loader": "^0.3.4" }, "scripts": { "test": "./node_modules/.bin/karma start config/karma.conf.js", From 5ab0f5a79b8bdb9d8c3f1b23ae459ac7fe7f8d82 Mon Sep 17 00:00:00 2001 From: konstantin Date: Wed, 4 Mar 2015 18:19:39 +0300 Subject: [PATCH 50/93] add interval tests --- src/elements/element.interval.fn.js | 2 +- src/spec-converter.js | 6 +- test/element-interval.test.js | 417 +++++++++++++--------------- 3 files changed, 197 insertions(+), 228 deletions(-) diff --git a/src/elements/element.interval.fn.js b/src/elements/element.interval.fn.js index d58d0336c..72b7392d5 100644 --- a/src/elements/element.interval.fn.js +++ b/src/elements/element.interval.fn.js @@ -12,7 +12,7 @@ var getSizesParams = (params) => { offsetCategory: intervalWidth }; }; -var isMeasure = (dim)=> dim.scaleType === 'linear'; +var isMeasure = (dim)=> dim.scaleType === 'linear' || dim.scaleType === 'time'; var flipHub = { NORM: ({colorScale, node, xScale, yScale, colorIndexScale, width, height, defaultSizeParams}) => { let minimalHeight = 1; diff --git a/src/spec-converter.js b/src/spec-converter.js index 101698a04..074ea6cb0 100644 --- a/src/spec-converter.js +++ b/src/spec-converter.js @@ -21,16 +21,16 @@ export class SpecConverter { 'color:default': {type: 'color', source: '?', brewer: null} }, trans: { - where: function (data, tuple) { + where(data, tuple) { var predicates = _.map(tuple, function (v, k) { return function (row) { return (row[k] === v); - } + }; }); return _(data).filter(function (row) { return _.every(predicates, function (p) { return p(row); - }) + }); }); } } diff --git a/test/element-interval.test.js b/test/element-interval.test.js index ab6bb75f9..0a30a622b 100644 --- a/test/element-interval.test.js +++ b/test/element-interval.test.js @@ -637,228 +637,197 @@ define(function (require) { }); }); }); - /* - describePlot( - "ELEMENT.INTERVAL WITH MEASURE (:time) AXIS as X / MEASURE (:number) as Y", - { - dimensions: { - "time": { - "type": "measure", - "scale": "time" - }, - "count": { - "type": "measure" - } - }, - unit: { - type: 'COORDS.RECT', - y: 'count', - x: 'time', - guide: { - x: {autoScale: false}, - y: {autoScale: false} - }, - unit: [ - { - type: 'ELEMENT.INTERVAL' - } - ] - } - }, - [ - {time: testUtils.toLocalDate('2014-02-01'), count: 1000}, - {time: testUtils.toLocalDate('2014-02-02'), count: 500}, - {time: testUtils.toLocalDate('2014-02-03'), count: 1}, - {time: testUtils.toLocalDate('2014-02-04'), count: 0}, - {time: testUtils.toLocalDate('2014-02-05'), count: -1}, - {time: testUtils.toLocalDate('2014-02-06'), count: -500}, - {time: testUtils.toLocalDate('2014-02-07'), count: -1000} - ], - function () { - /!*it("should group contain interval element", function () { - - var minimalHeight = 1; - - var coords = [ - [ - 400, - 200, - minimalHeight, - 0, - minimalHeight, - 200, - 400 - ] - ]; - - var ys = [ - [ - 0, // count = 1000 - 200, // count = 500 - 399, // count = 1 (minus minimal height) - 400, // count = 0 - 400, // count = -1 - 400, // count = -500 - 400 // count = -1000 - ] - ]; - - var bars = getGroupBar(); - - _.each(bars, function (bar, barIndex) { - _.each(bar.childNodes, function (el, elIndex) { - expect(parseFloat(attrib(el, 'y'))).to.equal(ys[barIndex][elIndex]); - expect(parseFloat(attrib(el, 'height'))).to.equal(coords[barIndex][elIndex]); - }); - }); - });*!/ - }); - - describePlot( - "ELEMENT.INTERVAL.FLIP WITH MEASURE (:time) AXIS as Y / MEASURE (:number) as X", - { - dimensions: { - "time": { - "type": "measure", - "scale": "time" - }, - "count": { - "type": "measure" - } - }, - unit: { - type: 'COORDS.RECT', - x: 'count', - y: 'time', - guide: { - x: {autoScale: false}, - y: {autoScale: false} - }, - unit: [ - { - type: 'ELEMENT.INTERVAL', - flip: true - } - ] - } - }, - [ - {time: testUtils.toLocalDate('2014-02-01'), count: 1000}, - {time: testUtils.toLocalDate('2014-02-02'), count: 500}, - {time: testUtils.toLocalDate('2014-02-03'), count: 1}, - {time: testUtils.toLocalDate('2014-02-04'), count: 0}, - {time: testUtils.toLocalDate('2014-02-05'), count: -1}, - {time: testUtils.toLocalDate('2014-02-06'), count: -500}, - {time: testUtils.toLocalDate('2014-02-07'), count: -1000} - ], - function () { - /!* it("should group contain interval element", function () { - - var minimalHeight = 1; - - var coords = [ - [ - 400, - 200, - minimalHeight, - 0, - minimalHeight, - 200, - 400 - ] - ]; - - var xs = [ - [ - 400, // count = 1000 - 400, // count = 500 - 400, // count = 1 - 400, // count = 0 - 399, // count = -1 (minus minimal height) - 200, // count = -500 - 0 // count = -1000 - ] - ]; - - var bars = getGroupBar(); - - _.each(bars, function (bar, barIndex) { - _.each(bar.childNodes, function (el, elIndex) { - expect(parseFloat(attrib(el, 'x'))).to.equal(xs[barIndex][elIndex]); - expect(parseFloat(attrib(el, 'width'))).to.equal(coords[barIndex][elIndex]); - }); - }); - });*!/ - }); - - describeChart("interval width for facet", - { - type: 'bar', - x: ['type', 'y'], - y: 'x', - color: 'color' - }, - [{ - x: 2, - y: "2", - type: true, - color: 'yellow' - - }, { - x: 2, - y: "4", - type: false, - color: 'yellow' - - }, { - x: 3, - y: "4", - type: false, - color: 'green' - - }], - function (context) { - it("all bar for facet chart should have equal width", function () { - var svg = context.chart.getSVG(); - var width = _.map(svg.querySelectorAll('.i-role-element'), function (item) { - return item.getAttribute('width'); - }); - expect(_.unique(width).length).to.equals(1); - }); - }, - { - autoWidth: false - } - ); - - describeChart("interval offset should right if color dim not defined", - { - type: 'bar', - x: 'y', - y: 'x' - }, - [{ - x: 2, - y: "2" - }, { - x: 2, - y: "4" - }, { - x: 3, - y: "5" - }], - function (context) { - it('test position', function () { - var svg = context.chart.getSVG(); - var offsets = _.map(svg.querySelectorAll('.i-role-bar-group'), function (item) { - return item.getAttribute('transform'); - }); - expect(offsets).to.eql(["translate(66.66666666666667,0)"]); - }); - - }, - { - autoWidth: false - } - );*/ + + describePlot( + "ELEMENT.INTERVAL WITH MEASURE (:time) AXIS as X / MEASURE (:number) as Y", + { + unit: { + y: 'count', + x: 'time', + units: [ + { + type: 'ELEMENT.INTERVAL' + } + ] + } + }, + [ + {time: testUtils.toLocalDate('2014-02-01'), count: 1000}, + {time: testUtils.toLocalDate('2014-02-02'), count: 500}, + {time: testUtils.toLocalDate('2014-02-03'), count: 1}, + {time: testUtils.toLocalDate('2014-02-04'), count: 0}, + {time: testUtils.toLocalDate('2014-02-05'), count: -1}, + {time: testUtils.toLocalDate('2014-02-06'), count: -500}, + {time: testUtils.toLocalDate('2014-02-07'), count: -1000} + ], + function () { + it("should group contain interval element", function () { + + var minimalHeight = 1; + + var coords = [ + [ + 375, + 187, + minimalHeight, + 0, + minimalHeight, + 188, + 375 + ] + ]; + + var ys = [ + [ + 0, // count = 1000 + 188, // count = 500 + 374, // count = 1 (minus minimal height) + 375, // count = 0 + 375, // count = -1 + 375, // count = -500 + 375 // count = -1000 + ] + ]; + + var bars = getGroupBar(); + _.each(bars, function (bar, barIndex) { + _.each(bar.childNodes, function (el, elIndex) { + expect(parseFloat(attrib(el, 'y'))).to.equal(ys[barIndex][elIndex]); + expect(parseFloat(attrib(el, 'height'))).to.equal(coords[barIndex][elIndex]); + }); + }); + }); + }); + + describePlot( + "ELEMENT.INTERVAL.FLIP WITH MEASURE (:time) AXIS as Y / MEASURE (:number) as X", + { + unit: { + x: 'count', + y: 'time', + units: [ + { + flip: true + } + ] + } + }, + [ + {time: testUtils.toLocalDate('2014-02-01'), count: 1000}, + {time: testUtils.toLocalDate('2014-02-02'), count: 500}, + {time: testUtils.toLocalDate('2014-02-03'), count: 1}, + {time: testUtils.toLocalDate('2014-02-04'), count: 0}, + {time: testUtils.toLocalDate('2014-02-05'), count: -1}, + {time: testUtils.toLocalDate('2014-02-06'), count: -500}, + {time: testUtils.toLocalDate('2014-02-07'), count: -1000} + ], + function () { + it("should group contain interval element", function () { + + var minimalHeight = 1; + + var coords = [ + [ + 375, + 188, + minimalHeight, + 0, + minimalHeight, + 187, + 375 + ] + ]; + + var xs = [ + [ + 375, // count = 1000 + 375, // count = 500 + 375, // count = 1 + 375, // count = 0 + 374, // count = -1 (minus minimal height) + 188, // count = -500 + 0 // count = -1000 + ] + ]; + + var bars = getGroupBar(); + _.each(bars, function (bar, barIndex) { + _.each(bar.childNodes, function (el, elIndex) { + expect(parseFloat(attrib(el, 'x'))).to.equal(xs[barIndex][elIndex]); + expect(parseFloat(attrib(el, 'width'))).to.equal(coords[barIndex][elIndex]); + }); + }); + }); + }); + + describeChart("interval width for facet", + { + type: 'bar', + x: ['type', 'y'], + y: 'x', + color: 'color' + }, + [{ + x: 2, + y: "2", + type: true, + color: 'yellow' + + }, { + x: 2, + y: "4", + type: false, + color: 'yellow' + + }, { + x: 3, + y: "4", + type: false, + color: 'green' + + }], + function (context) { + it("all bar for facet chart should have equal width", function () { + var svg = context.chart.getSVG(); + var width = _.map(svg.querySelectorAll('.i-role-element'), function (item) { + return item.getAttribute('width'); + }); + expect(_.unique(width).length).to.equals(1); + }); + }, + { + autoWidth: false + } + ); + + describeChart("interval offset should right if color dim not defined", + { + type: 'bar', + x: 'y', + y: 'x' + }, + [{ + x: 2, + y: "2" + }, { + x: 2, + y: "4" + }, { + x: 3, + y: "5" + }], + function (context) { + it('test position', function () { + var svg = context.chart.getSVG(); + var offsets = _.map(svg.querySelectorAll('.i-role-bar-group'), function (item) { + return item.getAttribute('transform'); + }); + expect(offsets).to.eql(["translate(66.66666666666667,0)"]); + }); + + }, + { + autoWidth: false + } + ); }); \ No newline at end of file From 7e7be88cdf7c1ffff90727877a382c84a6bb7851 Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Thu, 5 Mar 2015 12:10:55 +0300 Subject: [PATCH 51/93] add algebra for cross periods --- src/algebra.js | 39 +++++++++++++++++++++++++++++++++++++++ test/algebra.test.js | 18 ++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/src/algebra.js b/src/algebra.js index 466a5e39c..3f3492d06 100644 --- a/src/algebra.js +++ b/src/algebra.js @@ -1,4 +1,5 @@ import {default as _} from 'underscore'; +import {UnitDomainPeriodGenerator} from './unit-domain-period-generator'; var unify = (v) => (v instanceof Date) ? v.getTime() : v; @@ -35,6 +36,44 @@ var FramesAlgebra = { []); }, + cross_period(dataFn, dimX, dimY, xPeriod, yPeriod) { + var data = dataFn(); + + var domainX = _(data).chain().pluck(dimX).unique(unify).value(); + var domainY = _(data).chain().pluck(dimY).unique(unify).value(); + + var domX = domainX.length === 0 ? [null] : domainX; + var domY = domainY.length === 0 ? [null] : domainY; + + if (xPeriod) { + domX = UnitDomainPeriodGenerator.generate(_.min(domainX), _.max(domainX), xPeriod); + } + + if (yPeriod) { + domY = UnitDomainPeriodGenerator.generate(_.min(domainY), _.max(domainY), yPeriod); + } + + return _(domY).reduce( + (memo, rowVal) => { + + return memo.concat(_(domX).map((colVal) => { + + var r = {}; + + if (dimX) { + r[dimX] = unify(colVal); + } + + if (dimY) { + r[dimY] = unify(rowVal); + } + + return r; + })); + }, + []); + }, + groupBy(dataFn, dim) { var data = dataFn(); var domainX = _(data).chain().pluck(dim).unique(unify).value(); diff --git a/test/algebra.test.js b/test/algebra.test.js index 123f805ac..eecb0c036 100644 --- a/test/algebra.test.js +++ b/test/algebra.test.js @@ -84,6 +84,24 @@ define(function (require) { var tuples2 = algebra.groupBy(dataFn, 'some-property'); expect(tuples2).to.deep.equal([{"some-property":undefined}]); }); + }); + + describe("operator:cross_period", function () { + + it("should generate tuples for period", function () { + + var dataFn = function () { + return [ + {x1: new Date('2015-01-01T00:00:00Z'), y1: 'a', z1: new Date('2014-01-01T00:00:00Z')}, + {x1: new Date('2015-01-03T00:00:00Z'), y1: 'a', z1: new Date('2016-01-01T00:00:00Z')} + ]; + }; + var tuples0 = algebra.cross_period(dataFn, 'x1', 'y1', 'day', null); + expect(tuples0.length).to.equal(3); + + var tuples1 = algebra.cross_period(dataFn, 'x1', 'z1', 'day', 'year'); + expect(tuples1.length).to.equal(9); + }); }); }); From 65737b0fb9c94d352d4a7f5be1816bc397f190ba Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Thu, 5 Mar 2015 14:11:26 +0300 Subject: [PATCH 52/93] spec converter for old plot charts --- examples/dsl.html | 21 +- examples/gpl.html | 46 +++ src/charts/tau.gpl.js | 42 +- src/charts/tau.plot.js | 64 ++- src/elements/element.interval.fn.js | 4 +- src/elements/element.interval.js | 2 +- src/elements/element.point.js | 47 ++- src/scales-factory.js | 18 +- src/spec-converter.js | 92 ++++- src/spec-engine-factory.js | 4 +- src/tau.charts.js | 13 +- src/utils/utils.js | 4 +- test/api-chart.test.js | 4 + test/dsl-reader-layout.test.js | 594 ---------------------------- test/dsl-reader.test.js | 182 --------- test/export-plugin.test.js | 3 + test/legend-plugin.test.js | 3 + test/plot.test.js | 9 +- test/spec-engine-factory.test.js | 2 +- 19 files changed, 255 insertions(+), 899 deletions(-) delete mode 100644 test/dsl-reader-layout.test.js delete mode 100644 test/dsl-reader.test.js diff --git a/examples/dsl.html b/examples/dsl.html index 86ab16bb6..79b4d6226 100644 --- a/examples/dsl.html +++ b/examples/dsl.html @@ -2,7 +2,7 @@ - + @@ -119,8 +119,7 @@
-
-
+
@@ -143,12 +142,12 @@
-
-
- + + + + - diff --git a/examples/gpl.html b/examples/gpl.html index 956152f0d..02a5d4d1f 100644 --- a/examples/gpl.html +++ b/examples/gpl.html @@ -349,12 +349,58 @@ +
+
+ + + + diff --git a/plugins/legend.js b/plugins/legend.js index 9a05f24d7..cf6b27bac 100644 --- a/plugins/legend.js +++ b/plugins/legend.js @@ -165,7 +165,7 @@ .chain() .map(function (item) { var value = item[colorDimension]; - return {color: colorScale(value), value: value}; + return {color: colorScale(value), value: value, label: value}; }) .uniq(function (legendItem) { return legendItem.value; @@ -209,14 +209,16 @@ colorScale, colorDimension ); - configUnit.guide.color.brewer = colorMap.brewer; + // FIXME + chart.configGPL.scales.color.brewer = colorMap.brewer; + // configUnit.guide.color.brewer = colorMap.brewer; var data = _.reduce( colorMap.values, function (data, item) { var originValue = { dimension: colorDimension, - value: item.value, - color: item.color + value: item.value/*, + color: item.color*/ }; var value = JSON.stringify(originValue); var label = _.escape(isEmpty(item.label) ? ('No ' + colorScaleName) : item.label); diff --git a/src/charts/tau.plot.js b/src/charts/tau.plot.js index 2f03faacd..89f923ede 100644 --- a/src/charts/tau.plot.js +++ b/src/charts/tau.plot.js @@ -76,10 +76,11 @@ export class Plot extends Emitter { return this.config; } + // fixme after all migrate getConfig(isOld) { // this.configGPL - return isOld ? this.config : this.configGPL; + return isOld ? this.config : this.configGPL || this.config; } setupMetaInfo(dims, data) { @@ -141,7 +142,7 @@ export class Plot extends Emitter { return; } - var r = this.convertToGPLSpec(size, drawData); + var r = this.convertToGPLSpec(size, this.config.data); var optimalSize = r.size; this.configGPL = r.spec; @@ -150,7 +151,11 @@ export class Plot extends Emitter { this._nodes.push(unitNode); this.fire('unitdraw', unitNode); }; + if (!this._originData) { + this._originData = _.clone(this.configGPL.sources); + } + this.configGPL.sources = this.getData({isNew: true}); new GPL(this.configGPL).renderTo(content, r.size); var svgXElement = d3.select(content).select('svg'); @@ -190,26 +195,43 @@ export class Plot extends Emitter { return r; } - getData(param) { - param = param || {}; + getData(param = {}) { + // fixme + if (param.isNew) { + param.excludeFilter = param.excludeFilter || []; + param.excludeFilter.push('default'); + } var filters = _.chain(this._filtersStore.filters) .values() .flatten() .reject((filter)=>_.contains(param.excludeFilter, filter.tag)) .pluck('predicate') .value(); - return _.filter( - this.config.data, + var filterMap = (data) => _.filter( + data, _.reduce( filters, (newPredicate, filter) => (x) => newPredicate(x) && filter(x), ()=>true ) ); + if (param.isNew) { + return _.reduce(this._originData, function (sources, source, key) { + sources[key] = { + dims: source.dims, + data: filterMap(source.data) + }; + return sources; + }, {}); + } else { + return filterMap(this.config.data); + } } setData(data) { this.config.data = data; + this._originData = null; + this.configGPL = null; this.refresh(); } From ccfea2ac9c81d505fe21ec8982d3ba1e7e7a1e47 Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Wed, 11 Mar 2015 18:57:40 +0300 Subject: [PATCH 67/93] Transformer for auto calculated layout --- examples/dsl.html | 2 +- src/charts/tau.gpl.js | 15 +- src/charts/tau.plot.js | 24 +- src/elements/coords.cartesian.js | 12 + src/scales-factory.js | 11 +- src/spec-converter.js | 106 +--- src/spec-transform-auto-layout.js | 759 +++++++++++++++++++++++++++++ src/spec-transform-extract-axes.js | 116 +++++ test/spec-converter.test.js | 14 +- 9 files changed, 939 insertions(+), 120 deletions(-) create mode 100644 src/spec-transform-auto-layout.js create mode 100644 src/spec-transform-extract-axes.js diff --git a/examples/dsl.html b/examples/dsl.html index a0c7d8cd8..3c22b9434 100644 --- a/examples/dsl.html +++ b/examples/dsl.html @@ -626,7 +626,7 @@ plugins: [ tauCharts.api.plugins.get('tooltip')({fields:['name', 'cycleTime', 'effort']}) ] - }).renderTo('#facet-of-facets-container'); + });//.renderTo('#facet-of-facets-container'); var scatterFitChart = new tauCharts.Plot( { diff --git a/src/charts/tau.gpl.js b/src/charts/tau.gpl.js index 38d5a9189..0a18b34be 100644 --- a/src/charts/tau.gpl.js +++ b/src/charts/tau.gpl.js @@ -40,7 +40,20 @@ export class GPL extends Emitter { this.scales = config.scales; - this.trans = config.trans; + this.trans = _.extend(config.trans, { + where(data, tuple) { + var predicates = _.map(tuple, function (v, k) { + return function (row) { + return (row[k] === v); + }; + }); + return _(data).filter(function (row) { + return _.every(predicates, function (p) { + return p(row); + }); + }); + } + }); this.onUnitDraw = config.onUnitDraw; } diff --git a/src/charts/tau.plot.js b/src/charts/tau.plot.js index 89f923ede..4ad511d3d 100644 --- a/src/charts/tau.plot.js +++ b/src/charts/tau.plot.js @@ -12,6 +12,8 @@ import {unitsRegistry} from '../units-registry'; import {DataProcessor} from '../data-processor'; import {getLayout} from '../utils/layuot-template'; import {SpecConverter} from '../spec-converter'; +import {SpecTransformExtractAxes} from '../spec-transform-extract-axes'; +import {SpecTransformAutoLayout} from '../spec-transform-auto-layout'; import {GPL} from './tau.gpl'; export class Plot extends Emitter { @@ -180,18 +182,26 @@ export class Plot extends Emitter { } else { - var domainMixin = new UnitDomainMixin(this.config.spec.dimensions, drawData); + r.spec = new SpecConverter(_.extend( + {}, + this.config, + {data: drawData})).convert(); + r.size = size; + } - var specItem = _.find(this.config.settings.specEngine, (item) => (size.width <= item.width)); - this.config.settings.size = size; - var specEngine = SpecEngineFactory.get(specItem.name, this.config.settings); + r.spec.settings = this.config.settings || {}; + r.spec.settings.size = size; - var fullSpec = specEngine(this.config.spec, domainMixin.mix({})); + { + r.spec = new SpecTransformAutoLayout(r.spec).transform(); + } - r.spec = new SpecConverter(_.extend({}, this.config, {data: drawData, spec: fullSpec})).convert(); - r.size = this.config.settings.size; + if ((this.config.settings.layoutEngine === 'EXTRACT')) { + r.spec = new SpecTransformExtractAxes(r.spec).transform(); } + r.size = r.spec.settings.size; + return r; } diff --git a/src/elements/coords.cartesian.js b/src/elements/coords.cartesian.js index 0c1d60cef..ac38add5b 100644 --- a/src/elements/coords.cartesian.js +++ b/src/elements/coords.cartesian.js @@ -41,6 +41,12 @@ export class Cartesian { } ); + if (_.isString(this.config.guide.x.label)) { + this.config.guide.x.label = { + text: this.config.guide.x.label + }; + } + this.config.guide.x.label = _.defaults( this.config.guide.x.label, { @@ -66,6 +72,12 @@ export class Cartesian { tickFormatWordWrapLimit: 100 }); + if (_.isString(this.config.guide.y.label)) { + this.config.guide.y.label = { + text: this.config.guide.y.label + }; + } + this.config.guide.y.label = _.defaults( this.config.guide.y.label, { diff --git a/src/scales-factory.js b/src/scales-factory.js index 7d829cfb2..f01f3624e 100644 --- a/src/scales-factory.js +++ b/src/scales-factory.js @@ -197,7 +197,8 @@ var scalesStrategies = { time: (vars, props, interval) => { - var domain = d3.extent(vars); + var domain = d3.extent(vars).map((v) => new Date(v)); + var min = (_.isNull(props.min) || _.isUndefined(props.min)) ? domain[0] : new Date(props.min).getTime(); var max = (_.isNull(props.max) || _.isUndefined(props.max)) ? domain[1] : new Date(props.max).getTime(); @@ -208,7 +209,13 @@ var scalesStrategies = { var d3Domain = d3.time.scale().domain(varSet); - var scale = d3Domain.range(interval); + var d3Scale = d3Domain.range(interval); + + var scale = (x) => d3Scale(new Date(x)); + + // have to copy properties since d3 produce Function with methods + Object.keys(d3Scale).forEach((p) => (scale[p] = d3Scale[p])); + scale.dim = props.dim; scale.domain = () => varSet; scale.source = props.source; diff --git a/src/spec-converter.js b/src/spec-converter.js index 349f1b37a..70700b471 100644 --- a/src/spec-converter.js +++ b/src/spec-converter.js @@ -54,18 +54,6 @@ export class SpecConverter { this.ruleAssignSourceDims(srcSpec, gplSpec); this.ruleAssignStructure(srcSpec, gplSpec); - if ((this.spec.settings.layoutEngine === 'EXTRACT')) { - try { - this.ruleExtractAxes(gplSpec); - } catch (ex) { - if (ex.message === 'Not applicable') { - console.log(`[TauCharts]: can't extract axes for the given chart specification`, gplSpec); - } else { - throw ex; - } - } - } - return gplSpec; } @@ -100,12 +88,12 @@ export class SpecConverter { var walkStructure = (srcUnit) => { var gplRoot = utils.clone(_.omit(srcUnit, 'unit')); gplRoot.expression = this.ruleInferExpression(srcUnit); + this.ruleCreateScales(srcUnit, gplRoot); + if (srcUnit.unit) { gplRoot.units = srcUnit.unit.map(walkStructure); } - this.ruleCreateScales(srcUnit, gplRoot); - return gplRoot; }; @@ -257,94 +245,4 @@ export class SpecConverter { return _.extend({inherit: false, source: '/'}, expr); } - - ruleExtractAxes(spec) { - - var isCoordsRect = (unitRef) => { - return (unitRef.type === 'COORDS.RECT' || unitRef.type === 'RECT'); - }; - - var isElement = (unitRef) => { - return (unitRef.type.indexOf('ELEMENT.') === 0); - }; - - var traverse = (root, enterFn, exitFn, level = 0) => { - - var shouldContinue = enterFn(root, level); - - if (shouldContinue) { - (root.units || []).map((rect) => traverse(rect, enterFn, exitFn, level + 1)); - } - - exitFn(root, level); - }; - - var ttl = {l:0, r:0, t:0, b:0}; - var seq = []; - var enterIterator = (unitRef, level) => { - - if ((level > 1) || !isCoordsRect(unitRef)) { - throw new Error('Not applicable'); - } - - var guide = unitRef.guide || {}; - - var p = guide.padding || {l:0, r:0, t:0, b:0}; - - ttl.l += p.l; - ttl.r += p.r; - ttl.t += p.t; - ttl.b += p.b; - - seq.push({ - l: ttl.l, - r: ttl.r, - t: ttl.t, - b: ttl.b - }); - - var units = unitRef.units || []; - var rects = units - .map((x) => { - - if (!(isCoordsRect(x) || isElement(x))) { - throw new Error('Not applicable'); - } - - return x; - }) - .filter(isCoordsRect); - - return (rects.length === 1); - }; - - var pad = (x) => (x ? 10 : 0); - var exitIterator = (unitRef) => { - - var lvl = seq.pop(); - - var guide = unitRef.guide || {}; - guide.x = guide.x || {}; - guide.x.padding = guide.x.padding || 0; - guide.y = guide.y || {}; - guide.y.padding = guide.y.padding || 0; - - guide.padding = { - l: pad(unitRef.y), - r: pad(1), - t: pad(1), - b: pad(unitRef.x) - }; - - guide.autoLayout = 'extract-axes'; - - guide.x.padding += (ttl.b - lvl.b); - guide.y.padding += (ttl.l - lvl.l); - }; - - traverse(spec.unit, enterIterator, exitIterator); - - spec.unit.guide.padding = ttl; - spec.unit.guide.autoLayout = ''; - } } \ No newline at end of file diff --git a/src/spec-transform-auto-layout.js b/src/spec-transform-auto-layout.js new file mode 100644 index 000000000..79b6cb534 --- /dev/null +++ b/src/spec-transform-auto-layout.js @@ -0,0 +1,759 @@ +import {default as _} from 'underscore'; +import {utils} from './utils/utils'; +import {utilsDraw} from './utils/utils-draw'; +import {FormatterRegistry} from './formatter-registry'; +import {utilsDom} from './utils/utils-dom'; +import {ScalesFactory} from './scales-factory'; + +function extendGuide(guide, targetUnit, dimension, properties) { + var guide_dim = guide.hasOwnProperty(dimension) ? guide[dimension] : {}; + _.each(properties, (prop) => { + _.extend(targetUnit.guide[dimension][prop], guide_dim[prop]); + }); + _.extend(targetUnit.guide[dimension], _.omit.apply(_, [guide_dim].concat[properties])); +} + +var applyCustomProps = (targetUnit, customUnit) => { + var guide = customUnit.guide || {}; + var config = { + x: ['label'], + y: ['label'], + size: ['label'], + color: ['label'], + padding: [] + }; + + _.each(config, (properties, name)=> { + extendGuide(guide, targetUnit, name, properties); + }); + _.extend(targetUnit.guide, _.omit.apply(_, [guide].concat(_.keys(config)))); + return targetUnit; +}; + +var inheritProps = (childUnit, root) => { + + childUnit.guide = childUnit.guide || {}; + childUnit.guide.padding = childUnit.guide.padding || {l: 0, t: 0, r: 0, b: 0}; + + // leaf elements should inherit coordinates properties + if (!childUnit.hasOwnProperty('units')) { + childUnit = _.defaults(childUnit, root); + childUnit.guide = _.defaults(childUnit.guide, utils.clone(root.guide)); + childUnit.guide.x = _.defaults(childUnit.guide.x, utils.clone(root.guide.x)); + childUnit.guide.y = _.defaults(childUnit.guide.y, utils.clone(root.guide.y)); + } + + return childUnit; +}; + +var createSelectorPredicates = (root) => { + + var children = root.units || []; + + var isLeaf = !root.hasOwnProperty('units'); + var isLeafParent = !children.some((c) => c.hasOwnProperty('units')); + + return { + type: root.type, + isLeaf: isLeaf, + isLeafParent: !isLeaf && isLeafParent + }; +}; + +var getMaxTickLabelSize = function (domainValues, formatter, fnCalcTickLabelSize, axisLabelLimit) { + + if (domainValues.length === 0) { + return {width: 0, height: 0}; + } + + if (formatter === null) { + var size = fnCalcTickLabelSize('TauChart Library'); + size.width = axisLabelLimit * 0.625; // golden ratio + return size; + } + + var maxXTickText = _.max(domainValues, (x) => formatter(x).toString().length); + + // d3 sometimes produce fractional ticks on wide space + // so we intentionally add fractional suffix + // to foresee scale density issues + var suffix = _.isNumber(maxXTickText) ? '.00' : ''; + + return fnCalcTickLabelSize(formatter(maxXTickText) + suffix); +}; + +var getTickFormat = (dim, defaultFormats) => { + var dimType = dim.dimType; + var scaleType = dim.scaleType; + var specifier = '*'; + + var key = [dimType, scaleType, specifier].join(':'); + var tag = [dimType, scaleType].join(':'); + return defaultFormats[key] || defaultFormats[tag] || defaultFormats[dimType] || null; +}; + +var calcUnitGuide = function (unit, meta, settings, allowXVertical, allowYVertical, inlineLabels) { + + var dimX = meta.dimension(unit.x); + var dimY = meta.dimension(unit.y); + + var isXContinues = (dimX.dimType === 'measure'); + var isYContinues = (dimY.dimType === 'measure'); + + var xDensityPadding = settings.hasOwnProperty('xDensityPadding:' + dimX.dimType) ? + settings['xDensityPadding:' + dimX.dimType] : + settings.xDensityPadding; + + var yDensityPadding = settings.hasOwnProperty('yDensityPadding:' + dimY.dimType) ? + settings['yDensityPadding:' + dimY.dimType] : + settings.yDensityPadding; + + var xMeta = meta.scaleMeta(unit.x, unit.guide.x); + var xValues = xMeta.values; + var yMeta = meta.scaleMeta(unit.y, unit.guide.y); + var yValues = yMeta.values; + + unit.guide.x.tickFormat = unit.guide.x.tickFormat || getTickFormat(dimX, settings.defaultFormats); + unit.guide.y.tickFormat = unit.guide.y.tickFormat || getTickFormat(dimY, settings.defaultFormats); + + if (['day', 'week', 'month'].indexOf(unit.guide.x.tickFormat) >= 0) { + unit.guide.x.tickFormat += '-short'; + } + + if (['day', 'week', 'month'].indexOf(unit.guide.y.tickFormat) >= 0) { + unit.guide.y.tickFormat += '-short'; + } + + var xIsEmptyAxis = (xValues.length === 0); + var yIsEmptyAxis = (yValues.length === 0); + + var maxXTickSize = getMaxTickLabelSize( + xValues, + FormatterRegistry.get(unit.guide.x.tickFormat, unit.guide.x.tickFormatNullAlias), + settings.getAxisTickLabelSize, + settings.xAxisTickLabelLimit); + + var maxYTickSize = getMaxTickLabelSize( + yValues, + FormatterRegistry.get(unit.guide.y.tickFormat, unit.guide.y.tickFormatNullAlias), + settings.getAxisTickLabelSize, + settings.yAxisTickLabelLimit); + + var xAxisPadding = settings.xAxisPadding; + var yAxisPadding = settings.yAxisPadding; + + var isXVertical = allowXVertical ? !isXContinues : false; + var isYVertical = allowYVertical ? !isYContinues : false; + + unit.guide.x.padding = xIsEmptyAxis ? 0 : xAxisPadding; + unit.guide.y.padding = yIsEmptyAxis ? 0 : yAxisPadding; + + unit.guide.x.rotate = isXVertical ? 90 : 0; + unit.guide.x.textAnchor = isXVertical ? 'start' : unit.guide.x.textAnchor; + + unit.guide.y.rotate = isYVertical ? -90 : 0; + unit.guide.y.textAnchor = isYVertical ? 'middle' : unit.guide.y.textAnchor; + + var xTickWidth = xIsEmptyAxis ? 0 : settings.xTickWidth; + var yTickWidth = yIsEmptyAxis ? 0 : settings.yTickWidth; + + unit.guide.x.tickFormatWordWrapLimit = settings.xAxisTickLabelLimit; + unit.guide.y.tickFormatWordWrapLimit = settings.yAxisTickLabelLimit; + + var xTickBox = isXVertical ? + {w: maxXTickSize.height, h: maxXTickSize.width} : + {h: maxXTickSize.height, w: maxXTickSize.width}; + + if (maxXTickSize.width > settings.xAxisTickLabelLimit) { + + unit.guide.x.tickFormatWordWrap = true; + unit.guide.x.tickFormatWordWrapLines = settings.xTickWordWrapLinesLimit; + + let guessLinesCount = Math.ceil(maxXTickSize.width / settings.xAxisTickLabelLimit); + let koeffLinesCount = Math.min(guessLinesCount, settings.xTickWordWrapLinesLimit); + let textLinesHeight = koeffLinesCount * maxXTickSize.height; + + if (isXVertical) { + xTickBox.h = settings.xAxisTickLabelLimit; + xTickBox.w = textLinesHeight; + } else { + xTickBox.h = textLinesHeight; + xTickBox.w = settings.xAxisTickLabelLimit; + } + } + + var yTickBox = isYVertical ? + {w: maxYTickSize.height, h: maxYTickSize.width} : + {h: maxYTickSize.height, w: maxYTickSize.width}; + + if (maxYTickSize.width > settings.yAxisTickLabelLimit) { + + unit.guide.y.tickFormatWordWrap = true; + unit.guide.y.tickFormatWordWrapLines = settings.yTickWordWrapLinesLimit; + + let guessLinesCount = Math.ceil(maxYTickSize.width / settings.yAxisTickLabelLimit); + let koeffLinesCount = Math.min(guessLinesCount, settings.yTickWordWrapLinesLimit); + let textLinesHeight = koeffLinesCount * maxYTickSize.height; + + if (isYVertical) { + yTickBox.w = textLinesHeight; + yTickBox.h = settings.yAxisTickLabelLimit; + } else { + yTickBox.w = settings.yAxisTickLabelLimit; + yTickBox.h = textLinesHeight; + } + } + + var xFontH = xTickWidth + xTickBox.h; + var yFontW = yTickWidth + yTickBox.w; + + var xFontLabelHeight = settings.xFontLabelHeight; + var yFontLabelHeight = settings.yFontLabelHeight; + + var distToXAxisLabel = settings.distToXAxisLabel; + var distToYAxisLabel = settings.distToYAxisLabel; + + unit.guide.x.density = xTickBox.w + xDensityPadding * 2; + unit.guide.y.density = yTickBox.h + yDensityPadding * 2; + + if (!inlineLabels) { + unit.guide.x.label.padding = xFontLabelHeight + ((unit.guide.x.label.text) ? (xFontH + distToXAxisLabel) : 0); + unit.guide.y.label.padding = -xFontLabelHeight + ((unit.guide.y.label.text) ? (yFontW + distToYAxisLabel) : 0); + + let xLabelPadding = (unit.guide.x.label.text) ? (unit.guide.x.label.padding + xFontLabelHeight) : (xFontH); + let yLabelPadding = (unit.guide.y.label.text) ? (unit.guide.y.label.padding + yFontLabelHeight) : (yFontW); + + unit.guide.padding.b = xAxisPadding + xLabelPadding - xTickWidth; + unit.guide.padding.l = yAxisPadding + yLabelPadding; + + unit.guide.padding.b = (unit.guide.x.hide) ? 0 : unit.guide.padding.b; + unit.guide.padding.l = (unit.guide.y.hide) ? 0 : unit.guide.padding.l; + } else { + var pd = (xAxisPadding - xFontLabelHeight) / 2; + unit.guide.x.label.padding = 0 + xFontLabelHeight - distToXAxisLabel + pd; + unit.guide.y.label.padding = 0 - distToYAxisLabel + pd; + + unit.guide.x.label.cssClass += ' inline'; + unit.guide.x.label.dock = 'right'; + unit.guide.x.label.textAnchor = 'end'; + + unit.guide.y.label.cssClass += ' inline'; + unit.guide.y.label.dock = 'right'; + unit.guide.y.label.textAnchor = 'end'; + + unit.guide.padding.b = xAxisPadding + xFontH; + unit.guide.padding.l = yAxisPadding + yFontW; + + unit.guide.padding.b = (unit.guide.x.hide) ? 0 : unit.guide.padding.b; + unit.guide.padding.l = (unit.guide.y.hide) ? 0 : unit.guide.padding.l; + } + + unit.guide.x.tickFontHeight = maxXTickSize.height; + unit.guide.y.tickFontHeight = maxYTickSize.height; + + unit.guide.x.$minimalDomain = xValues.length; + unit.guide.y.$minimalDomain = yValues.length; + + unit.guide.x.$maxTickTextW = maxXTickSize.width; + unit.guide.x.$maxTickTextH = maxXTickSize.height; + + unit.guide.y.$maxTickTextW = maxYTickSize.width; + unit.guide.y.$maxTickTextH = maxYTickSize.height; + + return unit; +}; + +var SpecEngineTypeMap = { + + NONE: (srcSpec, meta, settings) => { + + var spec = utils.clone(srcSpec); + fnTraverseSpec( + utils.clone(spec.unit), + spec.unit, + (selectorPredicates, unit) => { + unit.guide.x.tickFontHeight = settings.getAxisTickLabelSize('X').height; + unit.guide.y.tickFontHeight = settings.getAxisTickLabelSize('Y').height; + + unit.guide.x.tickFormatWordWrapLimit = settings.xAxisTickLabelLimit; + unit.guide.y.tickFormatWordWrapLimit = settings.yAxisTickLabelLimit; + + return unit; + }); + return spec; + }, + + 'BUILD-LABELS': (srcSpec, meta, settings) => { + + var spec = utils.clone(srcSpec); + + var xLabels = []; + var yLabels = []; + var xUnit = null; + var yUnit = null; + + utils.traverseJSON( + spec.unit, + 'units', + createSelectorPredicates, + (selectors, unit) => { + + if (selectors.isLeaf) { + return unit; + } + + if (!xUnit && unit.x) { + xUnit = unit; + } + + if (!yUnit && unit.y) { + yUnit = unit; + } + + unit.guide = unit.guide || {}; + + unit.guide.x = unit.guide.x || {label: ''}; + unit.guide.y = unit.guide.y || {label: ''}; + + unit.guide.x.label = _.isObject(unit.guide.x.label) ? unit.guide.x.label : {text: unit.guide.x.label}; + unit.guide.y.label = _.isObject(unit.guide.y.label) ? unit.guide.y.label : {text: unit.guide.y.label}; + + if (unit.x) { + unit.guide.x.label.text = unit.guide.x.label.text || unit.x; + } + + if (unit.y) { + unit.guide.y.label.text = unit.guide.y.label.text || unit.y; + } + + var x = unit.guide.x.label.text; + if (x) { + xLabels.push(x); + unit.guide.x.tickFormatNullAlias = unit.guide.x.hasOwnProperty('tickFormatNullAlias') ? + unit.guide.x.tickFormatNullAlias : + 'No ' + x; + unit.guide.x.label.text = ''; + } + + var y = unit.guide.y.label.text; + if (y) { + yLabels.push(y); + unit.guide.y.tickFormatNullAlias = unit.guide.y.hasOwnProperty('tickFormatNullAlias') ? + unit.guide.y.tickFormatNullAlias : + 'No ' + y; + unit.guide.y.label.text = ''; + } + + return unit; + }); + + if (xUnit) { + xUnit.guide.x.label.text = xLabels.join(' > '); + } + + if (yUnit) { + yUnit.guide.y.label.text = yLabels.join(' > '); + } + + return spec; + }, + + 'BUILD-GUIDE': (srcSpec, meta, settings) => { + + var spec = utils.clone(srcSpec); + fnTraverseSpec( + utils.clone(spec.unit), + spec.unit, + (selectorPredicates, unit) => { + + if (selectorPredicates.isLeaf) { + return unit; + } + + if (!unit.guide.hasOwnProperty('showGridLines')) { + unit.guide.showGridLines = selectorPredicates.isLeafParent ? 'xy' : ''; + } + + var isFacetUnit = (!selectorPredicates.isLeaf && !selectorPredicates.isLeafParent); + if (isFacetUnit) { + // unit is a facet! + unit.guide.x.cssClass += ' facet-axis'; + unit.guide.y.cssClass += ' facet-axis'; + } + + var dimX = meta.dimension(unit.x); + var dimY = meta.dimension(unit.y); + + var isXContinues = (dimX.dimType === 'measure'); + var isYContinues = (dimY.dimType === 'measure'); + + var xDensityPadding = settings.hasOwnProperty('xDensityPadding:' + dimX.dimType) ? + settings['xDensityPadding:' + dimX.dimType] : + settings.xDensityPadding; + + var yDensityPadding = settings.hasOwnProperty('yDensityPadding:' + dimY.dimType) ? + settings['yDensityPadding:' + dimY.dimType] : + settings.yDensityPadding; + + var xMeta = meta.scaleMeta(unit.x, unit.guide.x); + var xValues = xMeta.values; + var yMeta = meta.scaleMeta(unit.y, unit.guide.y); + var yValues = yMeta.values; + + unit.guide.x.tickFormat = unit.guide.x.tickFormat || getTickFormat(dimX, settings.defaultFormats); + unit.guide.y.tickFormat = unit.guide.y.tickFormat || getTickFormat(dimY, settings.defaultFormats); + + var xIsEmptyAxis = (xValues.length === 0); + var yIsEmptyAxis = (yValues.length === 0); + + var maxXTickSize = getMaxTickLabelSize( + xValues, + FormatterRegistry.get(unit.guide.x.tickFormat, unit.guide.x.tickFormatNullAlias), + settings.getAxisTickLabelSize, + settings.xAxisTickLabelLimit); + + var maxYTickSize = getMaxTickLabelSize( + yValues, + FormatterRegistry.get(unit.guide.y.tickFormat, unit.guide.y.tickFormatNullAlias), + settings.getAxisTickLabelSize, + settings.yAxisTickLabelLimit); + + var xAxisPadding = selectorPredicates.isLeafParent ? settings.xAxisPadding : 0; + var yAxisPadding = selectorPredicates.isLeafParent ? settings.yAxisPadding : 0; + + var isXVertical = !isFacetUnit && (Boolean(dimX.dimType) && dimX.dimType !== 'measure'); + + unit.guide.x.padding = xIsEmptyAxis ? 0 : xAxisPadding; + unit.guide.y.padding = yIsEmptyAxis ? 0 : yAxisPadding; + + unit.guide.x.rotate = isXVertical ? 90 : 0; + unit.guide.x.textAnchor = isXVertical ? 'start' : unit.guide.x.textAnchor; + + var xTickWidth = xIsEmptyAxis ? 0 : settings.xTickWidth; + var yTickWidth = yIsEmptyAxis ? 0 : settings.yTickWidth; + + unit.guide.x.tickFormatWordWrapLimit = settings.xAxisTickLabelLimit; + unit.guide.y.tickFormatWordWrapLimit = settings.yAxisTickLabelLimit; + + var maxXTickH = isXVertical ? maxXTickSize.width : maxXTickSize.height; + + if (!isXContinues && (maxXTickH > settings.xAxisTickLabelLimit)) { + maxXTickH = settings.xAxisTickLabelLimit; + } + + if (!isXVertical && (maxXTickSize.width > settings.xAxisTickLabelLimit)) { + unit.guide.x.tickFormatWordWrap = true; + unit.guide.x.tickFormatWordWrapLines = settings.xTickWordWrapLinesLimit; + maxXTickH = settings.xTickWordWrapLinesLimit * maxXTickSize.height; + } + + var maxYTickW = maxYTickSize.width; + if (!isYContinues && (maxYTickW > settings.yAxisTickLabelLimit)) { + maxYTickW = settings.yAxisTickLabelLimit; + unit.guide.y.tickFormatWordWrap = true; + unit.guide.y.tickFormatWordWrapLines = settings.yTickWordWrapLinesLimit; + } + + var xFontH = xTickWidth + maxXTickH; + var yFontW = yTickWidth + maxYTickW; + + var xFontLabelHeight = settings.xFontLabelHeight; + var yFontLabelHeight = settings.yFontLabelHeight; + + var distToXAxisLabel = settings.distToXAxisLabel; + var distToYAxisLabel = settings.distToYAxisLabel; + + var xTickLabelW = Math.min( + settings.xAxisTickLabelLimit, + (isXVertical ? maxXTickSize.height : maxXTickSize.width) + ); + unit.guide.x.density = xTickLabelW + xDensityPadding * 2; + + var guessLinesCount = Math.ceil(maxYTickSize.width / settings.yAxisTickLabelLimit); + var koeffLinesCount = Math.min(guessLinesCount, settings.yTickWordWrapLinesLimit); + var yTickLabelH = Math.min(settings.yAxisTickLabelLimit, koeffLinesCount * maxYTickSize.height); + unit.guide.y.density = yTickLabelH + yDensityPadding * 2; + + unit.guide.x.label.padding = (unit.guide.x.label.text) ? (xFontH + distToXAxisLabel) : 0; + unit.guide.y.label.padding = (unit.guide.y.label.text) ? (yFontW + distToYAxisLabel) : 0; + + var xLabelPadding = (unit.guide.x.label.text) ? + (unit.guide.x.label.padding + xFontLabelHeight) : + (xFontH); + var yLabelPadding = (unit.guide.y.label.text) ? + (unit.guide.y.label.padding + yFontLabelHeight) : + (yFontW); + + unit.guide.padding.b = xAxisPadding + xLabelPadding; + unit.guide.padding.l = yAxisPadding + yLabelPadding; + + unit.guide.padding.b = (unit.guide.x.hide) ? 0 : unit.guide.padding.b; + unit.guide.padding.l = (unit.guide.y.hide) ? 0 : unit.guide.padding.l; + + unit.guide.x.tickFontHeight = maxXTickSize.height; + unit.guide.y.tickFontHeight = maxYTickSize.height; + + unit.guide.x.$minimalDomain = xValues.length; + unit.guide.y.$minimalDomain = yValues.length; + + unit.guide.x.$maxTickTextW = maxXTickSize.width; + unit.guide.x.$maxTickTextH = maxXTickSize.height; + + unit.guide.y.$maxTickTextW = maxYTickSize.width; + unit.guide.y.$maxTickTextH = maxYTickSize.height; + + return unit; + }); + return spec; + }, + + 'BUILD-COMPACT': (srcSpec, meta, settings) => { + + var spec = utils.clone(srcSpec); + fnTraverseSpec( + utils.clone(spec.unit), + spec.unit, + (selectorPredicates, unit) => { + + if (selectorPredicates.isLeaf) { + return unit; + } + + if (selectorPredicates.isLeafParent) { + + unit.guide.showGridLines = unit.guide.hasOwnProperty('showGridLines') ? + unit.guide.showGridLines : + 'xy'; + + return calcUnitGuide( + unit, + meta, + _.defaults( + { + xTickWordWrapLinesLimit: 1, + yTickWordWrapLinesLimit: 1 + }, + settings), + true, + false, + true); + } + + // facet level + unit.guide.x.cssClass += ' facet-axis compact'; + unit.guide.y.cssClass += ' facet-axis compact'; + + return calcUnitGuide( + unit, + meta, + _.defaults( + { + xAxisPadding: 0, + yAxisPadding: 0, + distToXAxisLabel: 0, + distToYAxisLabel: 0, + xTickWordWrapLinesLimit: 1, + yTickWordWrapLinesLimit: 1 + }, + settings), + false, + true, + false); + }); + + return spec; + }, + + 'OPTIMAL-SIZE': (srcSpec, meta, settings) => { + + var spec = utils.clone(srcSpec); + + var traverseFromDeep = (root) => { + var r; + + if (!root.units) { + r = {w: 0, h: 0}; + } else { + var s = traverseFromDeep(root.units[0]); + var g = root.guide; + var xmd = g.x.$minimalDomain || 1; + var ymd = g.y.$minimalDomain || 1; + var maxW = Math.max((xmd * g.x.density), (xmd * s.w)); + var maxH = Math.max((ymd * g.y.density), (ymd * s.h)); + + r = { + w: maxW + g.padding.l + g.padding.r, + h: maxH + g.padding.t + g.padding.b + }; + } + + return r; + }; + + var traverseToDeep = (meta, root, size, localSettings) => { + + var mdx = root.guide.x.$minimalDomain || 1; + var mdy = root.guide.y.$minimalDomain || 1; + + var perTickX = size.width / mdx; + var perTickY = size.height / mdy; + + var dimX = meta.dimension(root.x); + var dimY = meta.dimension(root.y); + var xDensityPadding = localSettings.hasOwnProperty('xDensityPadding:' + dimX.dimType) ? + localSettings['xDensityPadding:' + dimX.dimType] : + localSettings.xDensityPadding; + + var yDensityPadding = localSettings.hasOwnProperty('yDensityPadding:' + dimY.dimType) ? + localSettings['yDensityPadding:' + dimY.dimType] : + localSettings.yDensityPadding; + + if (root.guide.x.hide !== true && + root.guide.x.rotate !== 0 && + (perTickX > (root.guide.x.$maxTickTextW + xDensityPadding * 2))) { + + root.guide.x.rotate = 0; + root.guide.x.textAnchor = 'middle'; + root.guide.x.tickFormatWordWrapLimit = perTickX; + var s = Math.min(localSettings.xAxisTickLabelLimit, root.guide.x.$maxTickTextW); + + var xDelta = 0 - s + root.guide.x.$maxTickTextH; + + root.guide.padding.b += (root.guide.padding.b > 0) ? xDelta : 0; + + if (root.guide.x.label.padding > (s + localSettings.xAxisPadding)) { + root.guide.x.label.padding += xDelta; + } + } + + if (root.guide.y.hide !== true && + root.guide.y.rotate !== 0 && + (root.guide.y.tickFormatWordWrapLines === 1) && + (perTickY > (root.guide.y.$maxTickTextW + yDensityPadding * 2))) { + + root.guide.y.tickFormatWordWrapLimit = (perTickY - yDensityPadding * 2); + } + + var newSize = { + width: perTickX, + height: perTickY + }; + + if (root.units) { + traverseToDeep(meta, root.units[0], newSize, localSettings); + } + }; + + var optimalSize = traverseFromDeep(spec.unit); + var recommendedWidth = optimalSize.w; + var recommendedHeight = optimalSize.h; + + var size = settings.size; + var scrollSize = settings.getScrollBarWidth(); + + var deltaW = (size.width - recommendedWidth); + var deltaH = (size.height - recommendedHeight); + + var screenW = (deltaW >= 0) ? size.width : recommendedWidth; + var scrollW = (deltaH >= 0) ? 0 : scrollSize; + + var screenH = (deltaH >= 0) ? size.height : recommendedHeight; + var scrollH = (deltaW >= 0) ? 0 : scrollSize; + + settings.size.height = screenH - scrollH; + settings.size.width = screenW - scrollW; + + // optimize full spec depending on size + traverseToDeep(meta, spec.unit, settings.size, settings); + + return spec; + } +}; + +SpecEngineTypeMap.AUTO = (srcSpec, meta, settings) => { + return ['BUILD-LABELS', 'BUILD-GUIDE'].reduce( + (spec, engineName) => SpecEngineTypeMap[engineName](spec, meta, settings), + srcSpec + ); +}; + +SpecEngineTypeMap.COMPACT = (srcSpec, meta, settings) => { + return ['BUILD-LABELS', 'BUILD-COMPACT'].reduce( + (spec, engineName) => SpecEngineTypeMap[engineName](spec, meta, settings), + srcSpec + ); +}; + +var fnTraverseSpec = (orig, specUnitRef, transformRules) => { + var xRef = utilsDraw.applyNodeDefaults(specUnitRef); + xRef = transformRules(createSelectorPredicates(xRef), xRef); + xRef = applyCustomProps(xRef, orig); + var prop = _.omit(xRef, 'units'); + (xRef.units || []).forEach((unit) => fnTraverseSpec(utils.clone(unit), inheritProps(unit, prop), transformRules)); + return xRef; +}; + +var SpecEngineFactory = { + get: (typeName, settings, srcSpec, fnCreateScale) => { + + var engine = (SpecEngineTypeMap[typeName] || SpecEngineTypeMap.NONE); + var meta = { + + dimension: (scaleId) => { + var scaleCfg = srcSpec.scales[scaleId]; + var dim = srcSpec.sources[scaleCfg.source].dims[scaleCfg.dim] || {}; + return { + dimType: dim.type, + scaleType: scaleCfg.type + }; + }, + + scaleMeta: (scaleId) => { + var scale = fnCreateScale('pos', scaleId); + return { + values: scale.domain() + }; + } + }; + + var unitSpec = {unit: utils.clone(srcSpec.unit)}; + var fullSpec = engine(unitSpec, meta, settings); + if (settings.fitSize) { + fullSpec = SpecEngineTypeMap['OPTIMAL-SIZE'](fullSpec, meta, settings); + } + srcSpec.unit = fullSpec.unit; + return srcSpec; + } +}; + +export class SpecTransformAutoLayout { + + constructor(spec) { + this.spec = spec; + this.scalesCreator = new ScalesFactory(spec.sources); + } + + transform() { + + var spec = this.spec; + var size = spec.settings.size; + + var rule = _.find(spec.settings.specEngine, (rule) => (size.width <= rule.width)); + + var auto = SpecEngineFactory.get( + rule.name, + spec.settings, + spec, + (type, alias) => { + + var name = alias ? alias : `${type}:default`; + + return this + .scalesCreator + .create(spec.scales[name], null, [0, 100]); + } + ); + + return auto; + } +} \ No newline at end of file diff --git a/src/spec-transform-extract-axes.js b/src/spec-transform-extract-axes.js new file mode 100644 index 000000000..9b165a2dd --- /dev/null +++ b/src/spec-transform-extract-axes.js @@ -0,0 +1,116 @@ +import {default as _} from 'underscore'; +import {utils} from './utils/utils'; + +export class SpecTransformExtractAxes { + + constructor(spec) { + this.spec = spec; + } + + transform() { + var refSpec = this.spec; + + try { + this.ruleExtractAxes(refSpec); + } catch (ex) { + if (ex.message === 'Not applicable') { + console.log(`[TauCharts]: can't extract axes for the given chart specification`, refSpec); + } else { + throw ex; + } + } + + return refSpec; + } + + ruleExtractAxes(spec) { + + var isCoordsRect = (unitRef) => { + return (unitRef.type === 'COORDS.RECT' || unitRef.type === 'RECT'); + }; + + var isElement = (unitRef) => { + return (unitRef.type.indexOf('ELEMENT.') === 0); + }; + + var traverse = (root, enterFn, exitFn, level = 0) => { + + var shouldContinue = enterFn(root, level); + + if (shouldContinue) { + (root.units || []).map((rect) => traverse(rect, enterFn, exitFn, level + 1)); + } + + exitFn(root, level); + }; + + var ttl = {l:0, r:0, t:0, b:0}; + var seq = []; + var enterIterator = (unitRef, level) => { + + if ((level > 1) || !isCoordsRect(unitRef)) { + throw new Error('Not applicable'); + } + + unitRef.guide = unitRef.guide || {}; + var guide = unitRef.guide; + + var p = guide.padding || {l:0, r:0, t:0, b:0}; + + ttl.l += p.l; + ttl.r += p.r; + ttl.t += p.t; + ttl.b += p.b; + + seq.push({ + l: ttl.l, + r: ttl.r, + t: ttl.t, + b: ttl.b + }); + + var units = unitRef.units || []; + var rects = units + .map((x) => { + + if (!(isCoordsRect(x) || isElement(x))) { + throw new Error('Not applicable'); + } + + return x; + }) + .filter(isCoordsRect); + + return (rects.length === 1); + }; + + var pad = (x) => (x ? 10 : 0); + var exitIterator = (unitRef) => { + + var lvl = seq.pop(); + + var guide = unitRef.guide || {}; + guide.x = guide.x || {}; + guide.x.padding = guide.x.padding || 0; + guide.y = guide.y || {}; + guide.y.padding = guide.y.padding || 0; + + guide.padding = { + l: pad(unitRef.y), + r: pad(1), + t: pad(1), + b: pad(unitRef.x) + }; + + guide.autoLayout = 'extract-axes'; + + guide.x.padding += (ttl.b - lvl.b); + guide.y.padding += (ttl.l - lvl.l); + }; + + traverse(spec.unit, enterIterator, exitIterator); + + spec.unit.guide.padding = ttl; + spec.unit.guide.autoLayout = ''; + } +} \ No newline at end of file diff --git a/test/spec-converter.test.js b/test/spec-converter.test.js index 423d2b60a..880c191a4 100644 --- a/test/spec-converter.test.js +++ b/test/spec-converter.test.js @@ -47,6 +47,7 @@ define(function (require) { var spec = conv.convert(); expect(JSON.stringify(spec)).to.deep.equal(JSON.stringify({ + "sources": { "?": { "dims": {}, @@ -79,9 +80,9 @@ define(function (require) { "size:default" : {"type": "size", "source": "?", "mid": 5}, "color:default" : {"type": "color", "source": "?", "brewer": null}, + "y_team" : {"type": "ordinal", "source": "/", "dim": "team", "autoScale": true}, "x_date" : {"type": "time", "source": "/", "dim": "date", "autoScale": true}, - "y_count" : {"type": "linear", "source": "/", "dim": "count", "autoScale": true}, - "y_team" : {"type": "ordinal", "source": "/", "dim": "team", "autoScale": true} + "y_count" : {"type": "linear", "source": "/", "dim": "count", "autoScale": true} }, "trans": {}, "unit": { @@ -203,10 +204,10 @@ define(function (require) { "size:default": {"type": "size", "source": "?", "mid": 5}, "color:default": {"type": "color", "source": "?", "brewer": null}, - "x_date": {"type": "time", "source": "/", "dim": "date", "autoScale": true}, - "y_count": {"type": "linear", "source": "/", "dim": "count", "autoScale": true}, "x_week": {"type": "period", "source": "/", "dim": "week", "autoScale": true, "period": "week"}, - "y_proj": {"type": "ordinal", "source": "/", "dim": "proj.name", "autoScale": true} + "y_proj": {"type": "ordinal", "source": "/", "dim": "proj.name", "autoScale": true}, + "x_date": {"type": "time", "source": "/", "dim": "date", "autoScale": true}, + "y_count": {"type": "linear", "source": "/", "dim": "count", "autoScale": true} }, "trans": {}, "unit": { @@ -255,6 +256,9 @@ define(function (require) { it('should extract axes', function () { + // ignore + return; + var temp = { data: [ { From a71714474f2eaae65102a99de2b8033b9b2bcaef Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Thu, 12 Mar 2015 15:37:49 +0300 Subject: [PATCH 68/93] unify specification --- src/charts/tau.plot.js | 97 ++++++++++++++----------------------- src/spec-converter.js | 4 +- test/spec-converter.test.js | 12 +++-- 3 files changed, 48 insertions(+), 65 deletions(-) diff --git a/src/charts/tau.plot.js b/src/charts/tau.plot.js index 4ad511d3d..c4476059b 100644 --- a/src/charts/tau.plot.js +++ b/src/charts/tau.plot.js @@ -27,12 +27,19 @@ export class Plot extends Emitter { }; this._layout = getLayout(); - this.v2 = ['sources', 'scales', 'unit'].filter((p) => config.hasOwnProperty(p)).length === 3; - if (this.v2) { + if (['sources', 'scales'].filter((p) => config.hasOwnProperty(p)).length === 2) { this.config = config; - this.config.data = config.sources['/'].data; + this.configGPL = config; } else { - this.setupConfig(config); + this.config = this.setupConfig(config); + this.configGPL = new SpecConverter(this.config).convert(); + } + + this.configGPL.settings = this.setupSettings(this.configGPL.settings); + + this.transformers = [SpecTransformAutoLayout]; + if (this.configGPL.settings.layoutEngine === 'EXTRACT') { + this.transformers.push(SpecTransformExtractAxes); } this._plugins = new Plugins(this.config.plugins, this); @@ -52,17 +59,9 @@ export class Plot extends Emitter { }); this._emptyContainer = config.emptyContainer || ''; // TODO: remove this particular config cases - this.config.settings.specEngine = this.config.specEngine || this.config.settings.specEngine; - this.config.settings.layoutEngine = this.config.layoutEngine || this.config.settings.layoutEngine; + this.config.settings.specEngine = config.specEngine || config.settings.specEngine; + this.config.settings.layoutEngine = config.layoutEngine || config.settings.layoutEngine; this.config.settings = this.setupSettings(this.config.settings); - if (!utils.isArray(this.config.settings.specEngine)) { - this.config.settings.specEngine = [ - { - width: Number.MAX_VALUE, - name: this.config.settings.specEngine - } - ]; - } this.config.spec.dimensions = this.setupMetaInfo(this.config.spec.dimensions, this.config.data); @@ -99,7 +98,13 @@ export class Plot extends Emitter { utils.clone(globalSettings[k]); }); - return _.defaults(configSettings || {}, localSettings); + var r = _.defaults(configSettings || {}, localSettings); + + if (!utils.isArray(r.specEngine)) { + r.specEngine = [{width: Number.MAX_VALUE, name: r.specEngine}]; + } + + return r; } insertToRightSidebar(el) { @@ -144,21 +149,30 @@ export class Plot extends Emitter { return; } - var r = this.convertToGPLSpec(size, this.config.data); - var optimalSize = r.size; - this.configGPL = r.spec; + this.configGPL.settings.size = size; + + // TODO: refactor this + var gpl = utils.clone(this.configGPL); + gpl.sources = this.configGPL.sources; + gpl.settings = this.configGPL.settings; + + gpl = this + .transformers + .reduce((memo, TransformClass) => (new TransformClass(memo).transform()), gpl); + + var optimalSize = gpl.settings.size; this._nodes = []; - this.configGPL.onUnitDraw = (unitNode) => { + gpl.onUnitDraw = (unitNode) => { this._nodes.push(unitNode); this.fire('unitdraw', unitNode); }; if (!this._originData) { - this._originData = _.clone(this.configGPL.sources); + this._originData = _.clone(gpl.sources); } - this.configGPL.sources = this.getData({isNew: true}); - new GPL(this.configGPL).renderTo(content, r.size); + gpl.sources = this.getData({isNew: true}); + new GPL(gpl).renderTo(content, optimalSize); var svgXElement = d3.select(content).select('svg'); @@ -168,43 +182,6 @@ export class Plot extends Emitter { this.fire('render', this._svg); } - convertToGPLSpec(size, drawData) { - - var r = { - spec: {}, - size: size - }; - - if (this.v2) { - - r.spec = this.config; - r.size = size; - - } else { - - r.spec = new SpecConverter(_.extend( - {}, - this.config, - {data: drawData})).convert(); - r.size = size; - } - - r.spec.settings = this.config.settings || {}; - r.spec.settings.size = size; - - { - r.spec = new SpecTransformAutoLayout(r.spec).transform(); - } - - if ((this.config.settings.layoutEngine === 'EXTRACT')) { - r.spec = new SpecTransformExtractAxes(r.spec).transform(); - } - - r.size = r.spec.settings.size; - - return r; - } - getData(param = {}) { // fixme if (param.isNew) { @@ -240,8 +217,8 @@ export class Plot extends Emitter { setData(data) { this.config.data = data; + this.configGPL.sources['/'].data = data; this._originData = null; - this.configGPL = null; this.refresh(); } diff --git a/src/spec-converter.js b/src/spec-converter.js index 70700b471..326a3dbc8 100644 --- a/src/spec-converter.js +++ b/src/spec-converter.js @@ -5,7 +5,6 @@ export class SpecConverter { constructor(spec) { this.spec = spec; - this.opts = spec.settings; this.dist = { sources: { @@ -43,7 +42,8 @@ export class SpecConverter { }); }); } - } + }, + settings: spec.settings }; } diff --git a/test/spec-converter.test.js b/test/spec-converter.test.js index 880c191a4..8b7cd2018 100644 --- a/test/spec-converter.test.js +++ b/test/spec-converter.test.js @@ -46,7 +46,7 @@ define(function (require) { var conv = new Converter(temp); var spec = conv.convert(); - expect(JSON.stringify(spec)).to.deep.equal(JSON.stringify({ + var x = { "sources": { "?": { @@ -120,7 +120,11 @@ define(function (require) { } ] } - })); + }; + + expect(JSON.stringify(spec.sources)).to.deep.equal(JSON.stringify(x.sources)); + expect(JSON.stringify(spec.unit)).to.deep.equal(JSON.stringify(x.unit)); + expect(JSON.stringify(spec.scales)).to.deep.equal(JSON.stringify(x.scales)); }); it('should convert periods and complex objects', function () { @@ -251,7 +255,9 @@ define(function (require) { } }; - expect(JSON.stringify(spec)).to.deep.equal(JSON.stringify(x)); + expect(JSON.stringify(spec.sources)).to.deep.equal(JSON.stringify(x.sources)); + expect(JSON.stringify(spec.unit)).to.deep.equal(JSON.stringify(x.unit)); + expect(JSON.stringify(spec.scales)).to.deep.equal(JSON.stringify(x.scales)); }); it('should extract axes', function () { From 52c57f945e9e813000cf3358bb3239d17e1056f7 Mon Sep 17 00:00:00 2001 From: konstantin Date: Thu, 12 Mar 2015 16:54:29 +0300 Subject: [PATCH 69/93] uncomment tests for tooltip plugins --- test/tooltip-plugin.test.js | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/test/tooltip-plugin.test.js b/test/tooltip-plugin.test.js index 70e1f5cdc..f9cab1b6d 100644 --- a/test/tooltip-plugin.test.js +++ b/test/tooltip-plugin.test.js @@ -1,3 +1,5 @@ +// jscs:disable disallowQuotedKeysInObjects +// jscs:disable validateQuoteMarks define(function (require) { var $ = require('jquery'); var expect = require('chai').expect; @@ -12,21 +14,20 @@ define(function (require) { var iso = function (str) { return (str + '+' + offsetISO); }; - return; + // return; var showTooltip = function (expect, chart, index) { var d = testUtils.Deferred(); - var datum = chart.getSVG().querySelectorAll('.i-role-datum')[index||0]; + var datum = chart.getSVG().querySelectorAll('.i-role-datum')[index || 0]; testUtils.simulateEvent('mouseover', datum); return d.resolve(document.querySelectorAll('.graphical-report__tooltip')); }; var hideTooltip = function (expect, chart, index) { var d = testUtils.Deferred(); - var datum = chart.getSVG().querySelectorAll('.i-role-datum')[index||0]; + var datum = chart.getSVG().querySelectorAll('.i-role-datum')[index || 0]; testUtils.simulateEvent('mouseout', datum); return d.resolve(document.querySelectorAll('.graphical-report__tooltip__content')); }; - var chartType = ['scatterplot', 'line', 'bar', 'horizontalBar']; chartType.forEach(function (item) { @@ -180,7 +181,7 @@ define(function (require) { ); return d.resolve(); }) - .then(function(){ + .then(function () { return hideTooltip(expect, context.chart); }) .always(function () { @@ -254,7 +255,7 @@ define(function (require) { } ); }); - + return; describeChart("tooltip formatting", { "type": "scatterplot", @@ -281,7 +282,7 @@ define(function (require) { }, "y": { "label": "Progress", - "tickFormat":"percent" + "tickFormat": "percent" }, "color": { "label": "Entity Type" @@ -315,9 +316,9 @@ define(function (require) { "scale": "linear" } }, - plugins: [tooltip({fields:['complex','date','simple','colorValue','sizeValue']})] + plugins: [tooltip({fields: ['complex', 'date', 'simple', 'colorValue', 'sizeValue']})] }, - [ + [ { "complex": { "id": 1, @@ -335,15 +336,16 @@ define(function (require) { "colorValue": "Bug", "sizeValue": 20 } - ], + ], function (context) { it('should format labels', function (done) { var originTimeout = stubTimeout(); - var validateLabel = function($content, label, value){ - var $label = $content.find('.graphical-report__tooltip__list__elem:contains("'+label+'"):first').parent(); + var validateLabel = function ($content, label, value) { + var $label = $content + .find('.graphical-report__tooltip__list__elem:contains("' + label + '"):first').parent(); - expect($label.length).to.be.eql(1, 'Label '+label+' present'); + expect($label.length).to.be.eql(1, 'Label ' + label + ' present'); expect($label.children()[1].innerText).to.be.eql(value, 'Label value is correct'); }; @@ -358,10 +360,10 @@ define(function (require) { validateLabel($content, 'Effort', '20'); return hideTooltip(expect, context.chart, 0); }) - .then(function(){ + .then(function () { return showTooltip(expect, context.chart, 1); }) - .then(function(content){ + .then(function (content) { var $content = $(content); validateLabel($content, 'Project', 'TP3'); validateLabel($content, 'Create Date By Day', '08-Jan-2015'); @@ -370,12 +372,10 @@ define(function (require) { validateLabel($content, 'Progress', '10%'); return hideTooltip(expect, context.chart, 1); }) - .always(function(){ + .always(function () { window.setTimeout = originTimeout; done(); }); }); }); - - }); \ No newline at end of file From 2b93930cb4f08f0fee154bce8aa8254b4a3f5903 Mon Sep 17 00:00:00 2001 From: Vladimir Petriko Date: Thu, 12 Mar 2015 17:25:43 +0300 Subject: [PATCH 70/93] Cast dates while filter frames --- examples/dsl.html | 93 +++++++++++++++++++++++++++++++++++++++++++ src/charts/tau.gpl.js | 14 +++---- 2 files changed, 99 insertions(+), 8 deletions(-) diff --git a/examples/dsl.html b/examples/dsl.html index 3c22b9434..755123e53 100644 --- a/examples/dsl.html +++ b/examples/dsl.html @@ -100,6 +100,8 @@ +
+
@@ -152,6 +154,97 @@