Daniel@0: "use strict"; Daniel@0: Daniel@0: App.module("GraphicsRenderingModule", function(GraphicsRenderingModule, App, Backbone, Marionette, $, _, Logger) { Daniel@0: Daniel@0: GraphicsRenderingModule.addInitializer(function(options){ Daniel@0: Daniel@0: GraphicsRenderingModule.registerRenderer({ Daniel@0: id: "histogram", Daniel@0: inherit: "_", Daniel@0: Daniel@0: defaultVegaConfig: { Daniel@0: comparisonMode: null, Daniel@0: dataDefinition: null, // forXs, forBars, forFloats, forXMean, forXStdDev, forceXsAreEdges Daniel@0: Daniel@0: colorForFlats: "#666", Daniel@0: colorForBars: 0, Daniel@0: Daniel@0: ylabelDX: 3, Daniel@0: xlabelDY: -3, Daniel@0: paddingWhenAxisLabelsAreShown: {"top": 5, "left": 40, "bottom": 20, "right": 10}, Daniel@0: paddingWhenAxisLabelsAreHidden: {"top": 5, "left": 10, "bottom": 1, "right": 10}, Daniel@0: }, Daniel@0: Daniel@0: Daniel@0: _formVC: function(vc, data) { Daniel@0: vc.enoughSpaceForAxisLabels = vc.totalWidth > 200; Daniel@0: vc.padding = vc.enoughSpaceForAxisLabels ? vc.paddingWhenAxisLabelsAreShown : vc.paddingWhenAxisLabelsAreHidden; Daniel@0: vc.width = vc.totalWidth - vc.padding.left - vc.padding.right; Daniel@0: vc.height = vc.totalHeight - vc.padding.top - vc.padding.bottom; Daniel@0: Daniel@0: var xs; Daniel@0: var valuesForBars0 = []; Daniel@0: var valuesForBars1 = []; Daniel@0: var valuesForBars2 = []; Daniel@0: var valuesForFlats0 = []; Daniel@0: var valuesForFlats1 = []; Daniel@0: var valuesForFlats2 = []; Daniel@0: var valueForHoris0 = null; Daniel@0: var valuesForOverlays0 = []; Daniel@0: var xsAreEdges = false; Daniel@0: var xsAreCategories = false; Daniel@0: Daniel@0: if (vc.comparisonMode) { Daniel@0: xs = _.isFunction(vc.dataDefinition.forXs) ? vc.dataDefinition.forXs(data.left) : data.left[vc.dataDefinition.forXs]; Daniel@0: } else { Daniel@0: xs = _.isFunction(vc.dataDefinition.forXs) ? vc.dataDefinition.forXs(data.self) : data.self[vc.dataDefinition.forXs]; Daniel@0: } Daniel@0: Daniel@0: if (vc.comparisonMode) { Daniel@0: valuesForBars1 = (_.isFunction(vc.dataDefinition.forBars) ? vc.dataDefinition.forBars (data.left) : data.left [vc.dataDefinition.forBars] ); Daniel@0: valuesForBars2 = (_.isFunction(vc.dataDefinition.forBars) ? vc.dataDefinition.forBars (data.right) : data.right[vc.dataDefinition.forBars] ); Daniel@0: if (vc.dataDefinition.forFlats) { Daniel@0: valuesForFlats1 = (_.isFunction(vc.dataDefinition.forFlats) ? vc.dataDefinition.forFlats(data.left) : data.left [vc.dataDefinition.forFlats]); Daniel@0: valuesForFlats2 = (_.isFunction(vc.dataDefinition.forFlats) ? vc.dataDefinition.forFlats(data.right) : data.right[vc.dataDefinition.forFlats]); Daniel@0: } Daniel@0: } else { Daniel@0: valuesForBars0 = (_.isFunction(vc.dataDefinition.forBars) ? vc.dataDefinition.forBars (data.self) : data.self[vc.dataDefinition.forBars ]); Daniel@0: if (vc.dataDefinition.forFlats) { Daniel@0: valuesForFlats0 = (_.isFunction(vc.dataDefinition.forFlats) ? vc.dataDefinition.forFlats(data.self) : data.self[vc.dataDefinition.forFlats]); Daniel@0: } Daniel@0: } Daniel@0: Daniel@0: if (vc.comparisonMode == "direct") { Daniel@0: for (var i = 0; i < valuesForBars1.length; i++) { Daniel@0: valuesForBars0.push(valuesForBars2[i] - valuesForBars1[i]); Daniel@0: } Daniel@0: for (var i = 0; i < valuesForFlats1.length; i++) { Daniel@0: valuesForFlats0.push(valuesForFlats2[i] - valuesForFlats1[i]); Daniel@0: } Daniel@0: valuesForBars1 = []; Daniel@0: valuesForFlats1 = []; Daniel@0: valuesForBars2 = []; Daniel@0: valuesForFlats2 = []; Daniel@0: } Daniel@0: Daniel@0: try { Daniel@0: if (valuesForBars0.length == xs.length - 1) { Daniel@0: xsAreEdges = true; Daniel@0: } Daniel@0: if (valuesForBars1.length == xs.length - 1) { Daniel@0: xsAreEdges = true; Daniel@0: } Daniel@0: if (vc.dataDefinition.forceXsAreEdges) { Daniel@0: xsAreEdges = true; Daniel@0: } Daniel@0: } catch (e) { Daniel@0: throw new App.RepresentationModule.Error({type: "ok-count-0"}); Daniel@0: } Daniel@0: Daniel@0: if (_.isString(xs[0])) { Daniel@0: xsAreCategories = true; Daniel@0: } Daniel@0: Daniel@0: var xMean = undefined; Daniel@0: if (vc.dataDefinition.forXMean && data.self) { Daniel@0: xMean = _.isFunction(vc.dataDefinition.forXMean) ? vc.dataDefinition.forXMean (data.self) : data.self [vc.dataDefinition.forXMean]; Daniel@0: } Daniel@0: var xStdDev = undefined; Daniel@0: if (vc.dataDefinition.forXStdDev && data.self) { Daniel@0: xStdDev = _.isFunction(vc.dataDefinition.forXStdDev) ? vc.dataDefinition.forXStdDev (data.self) : data.self [vc.dataDefinition.forXStdDev]; Daniel@0: } Daniel@0: Daniel@0: //var vegaDataByName = {} Daniel@0: var generateVegaData = function(name, values, remarkPrefix, remarkSuffix) { Daniel@0: var vegaValues = []; Daniel@0: var tooltipType = xsAreEdges ? "tooltip_range" : "tooltip_point"; Daniel@0: var maxI = values.length; Daniel@0: for (var i = 0; i < maxI; i++) { Daniel@0: var nextX = xs[i+1]; Daniel@0: if (nextX !== undefined || xsAreCategories) { Daniel@0: vegaValues.push([tooltipType, values[i], xs[i], nextX, remarkPrefix, remarkSuffix]); Daniel@0: } Daniel@0: } Daniel@0: //console.log(vegaValues); Daniel@0: //vegaDataByName[name] = result; Daniel@0: return { Daniel@0: "name": name, Daniel@0: "values": vegaValues Daniel@0: }; Daniel@0: }; Daniel@0: Daniel@0: var generateVegaDataForHoris = function(name, values) { Daniel@0: return { Daniel@0: "name": name, Daniel@0: "values": ["tooltip_horis", value] Daniel@0: }; Daniel@0: }; Daniel@0: Daniel@0: if (valuesForBars0 .length) {vc.data.push(generateVegaData("bars0", valuesForBars0));} Daniel@0: if (valuesForBars1 .length) {vc.data.push(generateVegaData("bars1", valuesForBars1, "left: "));} Daniel@0: if (valuesForBars2 .length) {vc.data.push(generateVegaData("bars2", valuesForBars2, "right: "));} Daniel@0: Daniel@0: if (valuesForFlats0 .length) {vc.data.push(generateVegaData("flats0", valuesForFlats0));} Daniel@0: if (valuesForFlats1 .length) {vc.data.push(generateVegaData("flats1", valuesForFlats0, "left: "));} Daniel@0: if (valuesForFlats2 .length) {vc.data.push(generateVegaData("flats2", valuesForFlats0, "right: "));} Daniel@0: Daniel@0: if (valueForHoris0 !== null) {vc.data.push(generateVegaDataForHoris("horis0", valuesForOverlays0));} Daniel@0: Daniel@0: Daniel@0: var scalex = { Daniel@0: "name": "scalex", Daniel@0: "range": "width", Daniel@0: "zero": false, Daniel@0: //"nice": true, Daniel@0: "round": false, Daniel@0: }; Daniel@0: Daniel@0: if (xsAreCategories) { Daniel@0: scalex["domain"] = {"data": valuesForBars0.length ? "bars0" : "bars1", "field": "2"}; Daniel@0: scalex["type"] = "ordinal"; Daniel@0: scalex["padding"] = 0.2; Daniel@0: } else if (xsAreEdges) { Daniel@0: scalex["type"] = "linear"; Daniel@0: scalex["domain"] = [_.first(xs), _.last(xs)]; Daniel@0: scalex["points"] = true; Daniel@0: } else { Daniel@0: scalex["domain"] = {"data": valuesForBars0.length ? "bars0" : "bars1", "field": "2"}; Daniel@0: //scalex["type"] = "ordinal"; Daniel@0: //scalex["domain"] = xs; Daniel@0: //scalex["points"] = true; Daniel@0: scalex["domain"] = [_.first(xs), _.last(xs)]; Daniel@0: } Daniel@0: Daniel@0: var scalexRound = _.clone(scalex); Daniel@0: scalexRound.name = "scalex_round"; Daniel@0: scalexRound.round = true; Daniel@0: Daniel@0: vc.scales.push(scalex); Daniel@0: vc.scales.push(scalexRound); Daniel@0: Daniel@0: var mult = 1.1; Daniel@0: var generateVegaYScale = function(name, valuesForBars) { Daniel@0: var min = _.min(valuesForBars); Daniel@0: if (min < 0) { Daniel@0: min = min * mult; Daniel@0: } Daniel@0: var max = _.max(valuesForBars); Daniel@0: if (max > 0) { Daniel@0: max = max * mult; Daniel@0: } Daniel@0: return { Daniel@0: "name": name, Daniel@0: "range": "height", Daniel@0: "round": true, Daniel@0: "nice": false, Daniel@0: "domain": [Math.min(0, min), Math.max(0, max)] Daniel@0: }; Daniel@0: Daniel@0: }; Daniel@0: Daniel@0: if (valuesForBars0 .length) {vc.scales.push(generateVegaYScale("scale0", valuesForBars0));} Daniel@0: if (valuesForBars1 .length) {vc.scales.push(generateVegaYScale("scale1", valuesForBars1));} Daniel@0: if (valuesForBars2 .length) {vc.scales.push(generateVegaYScale("scale2", valuesForBars2));} Daniel@0: Daniel@0: //console.log(vegaScales); Daniel@0: //return; Daniel@0: Daniel@0: var barWidth = (vc.width / xs.length) + 0.5; Daniel@0: var offsetForXRight = .5; Daniel@0: // if (vegaConfig.width / xs.length > 1) { Daniel@0: // offsetForXRight = 0.5; Daniel@0: // } Daniel@0: var generateVegaMark = function(dataName, scale, type, color, opacity) { Daniel@0: var markProperties = { Daniel@0: //"fill": {"value": color}, Daniel@0: "opacity": {"value": opacity}, Daniel@0: "y": {"scale": scale, "field": "1"}, Daniel@0: }; Daniel@0: Daniel@0: if (type == "bar") { Daniel@0: markProperties["y2"] = {"scale": scale, "value": 0}; Daniel@0: } else { Daniel@0: markProperties["height"] = {"value": 2}; Daniel@0: } Daniel@0: if (xsAreCategories) { Daniel@0: markProperties["x"] = {"scale": "scalex", "field": "2"}; Daniel@0: markProperties["width"] = {"value": 1}; Daniel@0: markProperties["width"] = {"scale": "scalex", "band": true}; Daniel@0: } else if (xsAreEdges) { Daniel@0: markProperties["x"] = {"scale": "scalex", "field": "2"}; Daniel@0: markProperties["x2"] = {"scale": "scalex", "field": "3", "offset": offsetForXRight}; Daniel@0: } else { Daniel@0: markProperties["x"] = {"scale": "scalex", "field": "2", "offset": offsetForXRight / 2}; Daniel@0: //markProperties["x"] = {"scale": "scalex", "field": "2"}; Daniel@0: markProperties["width"] = {"value": barWidth}; Daniel@0: //markProperties["width"] = {"scale": "scalex", "band": true}; Daniel@0: } Daniel@0: return { Daniel@0: "type": "rect", Daniel@0: "from": {"data": dataName}, Daniel@0: "properties": { Daniel@0: "enter": markProperties, Daniel@0: "update": {"fill": {"value": color}}, Daniel@0: "hover": {"fill": {"value": "black"}} Daniel@0: } Daniel@0: }; Daniel@0: }; Daniel@0: Daniel@0: // background (to catch mouse events and remove tooltips) Daniel@0: vc.marks.push({ Daniel@0: "type": "rect", Daniel@0: "properties": { Daniel@0: "enter": { Daniel@0: "x": {"value": -vc.padding.left}, Daniel@0: "y": {"value": -vc.padding.top}, Daniel@0: "fill": {"value": "#fff"}, Daniel@0: "y2": {"field": {"group": "height"}, "offset": vc.padding.left + vc.padding.right}, Daniel@0: "x2": {"field": {"group": "width"}, "offset": vc.padding.top + vc.padding.bottom}, Daniel@0: } Daniel@0: } Daniel@0: }); Daniel@0: Daniel@0: Daniel@0: // Marks Daniel@0: if (valuesForBars0 .length) {vc.marks.push(generateVegaMark("bars0", "scale0", "bar", vc.colorForBars, 1));} Daniel@0: if (valuesForBars1 .length) {vc.marks.push(generateVegaMark("bars1", "scale1", "bar", vc.colorForBars, .5));} Daniel@0: if (valuesForBars2 .length) {vc.marks.push(generateVegaMark("bars2", "scale2", "bar", vc.colorForBars, .5));} Daniel@0: Daniel@0: if (valuesForFlats0.length) {vc.marks.push(generateVegaMark("flats0", "scale0", "flat", vc.colorForFlats, 1));} Daniel@0: if (valuesForFlats1.length) {vc.marks.push(generateVegaMark("flats1", "scale1", "flat", vc.colorForFlats, .5));} Daniel@0: if (valuesForFlats2.length) {vc.marks.push(generateVegaMark("flats2", "scale2", "flat", vc.colorForFlats, .5));} Daniel@0: Daniel@0: // y axis Daniel@0: vc.marks.push({ Daniel@0: "type": "rect", Daniel@0: "properties": { Daniel@0: "enter": { Daniel@0: "fill": {"value": vc.colorForAxes}, Daniel@0: "x": {"value": 0}, Daniel@0: "x2": {"field": {"group": "width"}}, Daniel@0: "height": {"value": 1}, Daniel@0: "y": {"scale": valuesForBars0.length ? "scale0" : "scale1", "value": 0}, Daniel@0: } Daniel@0: } Daniel@0: }); Daniel@0: Daniel@0: var generateVegaVerticalMark = function(x, color, dataName, strokeDash) { Daniel@0: return { Daniel@0: "type": "rect", Daniel@0: "from": { Daniel@0: "data": dataName, Daniel@0: }, Daniel@0: "properties": { Daniel@0: "enter": { Daniel@0: "stroke": {"value": vc.colorForAxisLabels}, Daniel@0: "strokeWidth": {"value": 1}, Daniel@0: "y": {"value": -99}, Daniel@0: "y2": {"field": {"group": "height"}}, Daniel@0: "x": {"scale": "scalex_round", "value": x}, Daniel@0: //"x2": {"scale": "scalex", "value": x, "offset": ""}, Daniel@0: "width": {"value": 1}, Daniel@0: "strokeDash": {"value": strokeDash} Daniel@0: } Daniel@0: } Daniel@0: }; Daniel@0: }; Daniel@0: Daniel@0: // x mean, x std dev Daniel@0: if (xStdDev !== undefined && xMean !== undefined) { Daniel@0: vc.data.push({ Daniel@0: "name": "stdDev", Daniel@0: "values": [["tooltip_value", xStdDev, 0, 0, "standard deviation: "]] Daniel@0: }); Daniel@0: vc.marks.push(generateVegaVerticalMark(xMean - xStdDev, "#ccc", "stdDev", [1, 1])); Daniel@0: vc.marks.push(generateVegaVerticalMark(xMean + xStdDev, "#ccc", "stdDev", [1, 1])); Daniel@0: } Daniel@0: if (xMean !== undefined) { Daniel@0: vc.data.push({ Daniel@0: "name": "mean", Daniel@0: "values": [["tooltip_value", xMean, 0, 0, "mean: ", xStdDev === 0 ? " (standard deviation is zero)" : undefined]] Daniel@0: }); Daniel@0: vc.marks.push(generateVegaVerticalMark(xMean, "#666", "mean", 0)); Daniel@0: } Daniel@0: Daniel@0: Daniel@0: //Axes Daniel@0: var axisOpacity = vc.enoughSpaceForAxisLabels ? {"value" : 1} : {"value" : 0}; Daniel@0: Daniel@0: vc.axes.push({"type": "x", "scale": "scalex", /*"ticks": 8,*/ Daniel@0: "properties": { Daniel@0: "axis": { Daniel@0: "stroke": {"value": "#fff"}, Daniel@0: "strokeWidth": {"value": 0} Daniel@0: }, Daniel@0: "grid": { Daniel@0: "stroke": {"value": "#fff"}, Daniel@0: "strokeOpacity": {"value": 0.4}, Daniel@0: "strokeWidth": {"value": 1} Daniel@0: }, Daniel@0: "ticks": { Daniel@0: "stroke": {"value": "#fff"}, Daniel@0: "strokeWidth": {"value": 0}, Daniel@0: "opacity": axisOpacity Daniel@0: }, Daniel@0: "labels": { Daniel@0: "fill": {"value": vc.colorForAxisLabels}, Daniel@0: "dy": {"value": vc.xlabelDY}, Daniel@0: "font": {"value": vc.fontFace}, Daniel@0: "fontSize": {"value": vc.fontSizeForLabelsInAxis}, Daniel@0: "opacity": axisOpacity Daniel@0: } Daniel@0: } Daniel@0: }); Daniel@0: if (valuesForBars0.length) { Daniel@0: vc.axes.push({"type": "y", "scale": "scale0", /*"ticks": 5,*/ "grid": vc.enoughSpaceForAxisLabels, "zero": false, Daniel@0: "properties": { Daniel@0: "axis": { Daniel@0: "stroke": {"value": "#fff"}, Daniel@0: "strokeWidth": {"value": 0} Daniel@0: }, Daniel@0: "grid": { Daniel@0: "stroke": {"value": "#fff"}, Daniel@0: "strokeOpacity": {"value": 0.4}, Daniel@0: "opacity": {"value": 1} Daniel@0: }, Daniel@0: "ticks": { Daniel@0: "stroke": {"value": "#fff"}, Daniel@0: "strokeWidth": {"value": 0}, Daniel@0: "opacity": axisOpacity Daniel@0: }, Daniel@0: "labels": { Daniel@0: "fill": {"value": vc.colorForAxisLabels}, Daniel@0: "fontSize": {"value": vc.fontSizeForLabelsInAxis}, Daniel@0: "dx": {"value": vc.ylabelDX}, Daniel@0: "font": {"value": vc.fontFace}, Daniel@0: "opacity": axisOpacity Daniel@0: } Daniel@0: } Daniel@0: }); Daniel@0: }; Daniel@0: }, Daniel@0: }); Daniel@0: }); Daniel@0: }, Logger);