diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1da0383 --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +# build fodler +build + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# ========================= +# Operating System Files +# ========================= + +# OSX +# ========================= + +.DS_Store +.AppleDouble +.LSOverride + +# Thumbnails +._* + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk diff --git a/.sense-go.yml b/.sense-go.yml new file mode 100644 index 0000000..aebf586 --- /dev/null +++ b/.sense-go.yml @@ -0,0 +1,11 @@ +packageName: "barsPlus" +wbfolder: + enabled: true +watch: + - "./src/**/*.*" +deployment: + toLocal: + enabled: true + pathFetching: false + localExtensionsBaseDir: "C:\\Users\\thaenig\\Documents\\Qlik\\Sense\\Extensions" + extensionDirOverride: "barsPlus" \ No newline at end of file diff --git a/barsPlus-props.js b/barsPlus-props.js deleted file mode 100644 index 234875c..0000000 --- a/barsPlus-props.js +++ /dev/null @@ -1,899 +0,0 @@ -define( [ ], -/** - * barsPlus Properties - set up appearance of accordion properties panel in Qlik Sense - * - * Defaults are defined here. - * Settings modify objects in object: layout.props - * - * Author: L. Woodside - * Modification History: - * - * Version Person Date Description - * V1.0.0 L. Woodside 19-Dec-2016 Initial Release - * V1.1.0 L. Woodside 29-Dec-2016 Added text on bars - * V1.2.0 L. Woodside 07-Jan-2017 Allow multiple measures - * V1.3.0 L. Woodside 15-Jan-2017 Improved color options - * V1.3.1 L. Woodside 27-Jan-2017 Fix problem with legend properties - * -*/ -function () { - 'use strict'; - var dimensions = { - uses: "dimensions", - min: 0, - max: 2, - items: { - attribute: { - type: "string", - ref: "qAttributeExpressions.0.qExpression", - label: "Dimension attribute", - expression: "always", - defaultValue: "" - } - } - }; - var measures = { - uses: "measures", - min: 1, - max: 10, - items: { - attribute: { - type: "string", - component: "expression", - ref: "qAttributeExpressions.0.qExpression", - label: "Measure attribute", - expression: "always", - defaultValue: "" - } - } - }; - var sorting = { - uses: "sorting" - }; - var addons = { - uses: "addons" - ,items: { - dataHandling: { - uses: "dataHandling" - }, - selectionMode: { - type: "string", - component: "dropdown", - label: "Selection mode", - ref: "selectionMode", - defaultValue: "CONFIRM", - options: [ - { value: "CONFIRM", label: "Standard"}, - { value: "QUICK", label: "Quick"} - ] - } - } - }; - - var appearance = { - uses: "settings", - items: { - presentation: { - type: "items", - label: "Presentation", - items: { - orientation: { - type: "string", - component: "dropdown", - label: "Orientation", - ref: "props.orientation", - defaultValue: "V", - options: [ - { value: "V", label: "Vertical bars"}, - { value: "H", label: "Horizontal bars"}, - ] - }, - normalized: { - type: "boolean", - component: "switch", - label: "100% bar chart", - ref: "props.normalized", - defaultValue: false, - options: [ - { value: false, label: "Not 100%"}, - { value: true, label: "100% bars"} - ], - show: function(data) { - return (data.qHyperCubeDef.qDimensions.length > 1 - || (data.qHyperCubeDef.qDimensions.length == 1 - && data.qHyperCubeDef.qMeasures.length > 1 - )); - } - }, - showDeltas: { - type: "boolean", - component: "switch", - label: "Show bar connectors", - ref: "props.showDeltas", - defaultValue: false, - options: [ - { value: false, label: "Standard bars"}, - { value: true, label: "Bars with connectors"} - ], - show: function(data) { - return (data.qHyperCubeDef.qDimensions.length > 1 - || (data.qHyperCubeDef.qDimensions.length == 1 - && data.qHyperCubeDef.qMeasures.length > 1 - )); - } - }, - barSpacing: { - type: "number", - label: "Bar spacing", - ref: "props.barSpacing", - defaultValue: 0.3, - expression: "optional" - }, - barSpacing2: { - type: "number", - component: "slider", - ref: "props.barSpacing", - defaultValue: 0.3, - min: 0, - max: 1.001, // Excludes 1 otherwise - step: 0.1 - }, - outerGap: { - type: "number", - label: "Outer bar spacing", - ref: "props.outerGap", - defaultValue: 0.1, - expression: "optional" - }, - outerGap2: { - type: "number", - component: "slider", - ref: "props.outerGap", - defaultValue: 0.1, - min: 0, - max: 1.001, // Excludes 1 otherwise - step: 0.1 - }, - gridHeight: { - type: "number", - label: "Grid height relative to max bar", - ref: "props.gridHeight", -// defaultValue: 1.1, - expression: "optional" - }, - gridHeight2: { - type: "number", - component: "slider", -// label: "Grid height relative to max", - ref: "props.gridHeight", - defaultValue: 1.1, - min: 1, - max: 2.001, - step: 0.1 - }, - backgroundColor: { - type: "string", - label: "Background color", - ref: "props.backgroundColor", - defaultValue: "white", - expression: "optional" - }, - } - }, - colors: { - type: "items", - label: "Colors and Legend", - items: { - colorSource: { - type: "string", - component: "dropdown", - label: "Color source", - ref: "props.colorSource", - defaultValue: "A", - options: [ - { value: "A", label: "Assigned"}, - { value: "C", label: "Calculated"} - ] - }, - colorAttr: { - type: "string", - component: "dropdown", - label: "Color attribute", - ref: "props.colorAttr", - defaultValue: "O", - options: [ - { value: "O", label: "Attribute is offset in scheme"}, - { value: "C", label: "Attribute is color value"} - ], - show: function(data) { - return data.props.colorSource == "C"; - } - }, - colorScheme: { - type: "string", - component: "dropdown", - label: "Color scheme", - ref: "props.colorScheme", - defaultValue: "category20b", - options: [ - { value: "category20b", label: "category20b"}, - { value: "category20c", label: "category20c"}, - { value: "category20", label: "catagory20"}, - { value: "category10", label: "category10"}, - { value: "google20", label: "google20"}, - { value: "custom100", label: "custom100"}, - { value: "qlikView18", label: "qlikView18"}, - { value: "qlikSense12", label: "qlikSense12"} - ], - show: function(data) { - return (data.props.colorSource != "C" - || (data.props.colorSource == "C" && data.props.colorAttr == "O")); - } - }, - colorOffset: { - type: "number", - label: "Start offset in color scheme", - ref: "props.colorOffset", - defaultValue: 0, - expression: "optional", - show: function(data) { - return data.props.colorSource != "C"; - } - }, - singleColor: { - type: "boolean", - component: "switch", - label: "Single Color", - ref: "props.singleColor", - defaultValue: true, - options: [ - { value: true, label: "Single Color"}, - { value: false, label: "Multi-color"} - ], - show: function(data) { - return ((data.qHyperCubeDef.qDimensions.length == 0 - || (data.qHyperCubeDef.qDimensions.length == 1 - && data.qHyperCubeDef.qMeasures.length == 1 - )) && data.props.colorSource != "C"); - } - }, - showLegend: { - type: "boolean", - component: "switch", - label: "Show legend", - ref: "props.showLegend", - defaultValue: false, - options: [ - { value: false, label: "No legend"}, - { value: true, label: "Show legend"} - ], - show: function(data) { - return ( - ( - ( - (data.qHyperCubeDef.qDimensions.length == 0 && data.qHyperCubeDef.qMeasures.length > 1) - || (data.qHyperCubeDef.qDimensions.length == 1 && data.qHyperCubeDef.qMeasures.length == 1) - ) - && ( - (data.props.colorSource != "C" && !data.props.singleColor) - || data.props.colorSource == "C" - ) - ) - || data.qHyperCubeDef.qDimensions.length > 1 - || (data.qHyperCubeDef.qDimensions.length == 1 && data.qHyperCubeDef.qMeasures.length > 1) - ); - } - }, - legendPosition: { - type: "string", - component: "dropdown", - label: "Legend position", - ref: "props.legendPosition", - defaultValue: "R", - options: [ - { value: "R", label: "Right"}, - { value: "L", label: "Left"}, - { value: "T", label: "Top"}, - { value: "B", label: "Bottom"} - ], - show: function(data) { - return data.props.showLegend && ( - ( - ( - (data.qHyperCubeDef.qDimensions.length == 0 && data.qHyperCubeDef.qMeasures.length > 1) - || (data.qHyperCubeDef.qDimensions.length == 1 && data.qHyperCubeDef.qMeasures.length == 1) - ) - && ( - (data.props.colorSource != "C" && !data.props.singleColor) - || data.props.colorSource == "C" - ) - ) - || data.qHyperCubeDef.qDimensions.length > 1 - || (data.qHyperCubeDef.qDimensions.length == 1 && data.qHyperCubeDef.qMeasures.length > 1) - ); - } - }, - legendSize: { - type: "string", - component: "dropdown", - label: "Legend size", - ref: "props.legendSize", - defaultValue: "M", - options: [ - { value: "N", label: "Narrow"}, - { value: "M", label: "Medium"}, - { value: "W", label: "Wide"} - ], - show: function(data) { - return data.props.showLegend && ( - ( - ( - (data.qHyperCubeDef.qDimensions.length == 0 && data.qHyperCubeDef.qMeasures.length > 1) - || (data.qHyperCubeDef.qDimensions.length == 1 && data.qHyperCubeDef.qMeasures.length == 1) - ) - && ( - (data.props.colorSource != "C" && !data.props.singleColor) - || data.props.colorSource == "C" - ) - ) - || data.qHyperCubeDef.qDimensions.length > 1 - || (data.qHyperCubeDef.qDimensions.length == 1 && data.qHyperCubeDef.qMeasures.length > 1) - ); - } - }, - legendSpacing: { - type: "string", - component: "dropdown", - label: "Legend item spacing", - ref: "props.legendSpacing", - defaultValue: "M", - options: [ - { value: "N", label: "Narrow"}, - { value: "M", label: "Medium"}, - { value: "W", label: "Wide"} - ], - show: function(data) { - return data.props.showLegend && (data.props.legendPosition == "T" || data.props.legendPosition == "B") && ( - ( - ( - (data.qHyperCubeDef.qDimensions.length == 0 && data.qHyperCubeDef.qMeasures.length > 1) - || (data.qHyperCubeDef.qDimensions.length == 1 && data.qHyperCubeDef.qMeasures.length == 1) - ) - && ( - (data.props.colorSource != "C" && !data.props.singleColor) - || data.props.colorSource == "C" - ) - ) - || data.qHyperCubeDef.qDimensions.length > 1 - || (data.qHyperCubeDef.qDimensions.length == 1 && data.qHyperCubeDef.qMeasures.length > 1) - ); - } - } - } - }, - dimensionAxis: { - type: "items", - label: function(data) { - var t = ""; - if (data.qHyperCubeDef.qDimensions.length == 0) { - if (data.props.axisTitleD.length > 0) - t = " (" + data.props.axisTitleD + ")" - } - else { - t = data.qHyperCubeDef.qDimensions[0].qDef.qFieldLabels[0]; - if (t.length == 0) { - t = data.qHyperCubeDef.qDimensions[0].qDef.qFieldDefs[0]; - } - t = " (" + (t.startsWith("=") ? t.slice(1) : t) + ")"; - }; - return (data.props.orientation == "V" ? "X" : "Y") + "-Axis" + t; - }, - items: { - LabelTitleD: { - type: "string", - component: "dropdown", - label: "Labels and title", - ref: "props.labelTitleD", - defaultValue: "B", - options: [ - { value: "B", label: "Labels and title"}, - { value: "L", label: "Labels only"}, - { value: "T", label: "Titles only"}, - { value: "N", label: "None"} - ] - }, - axisTitleD: { - type: "string", - label: "Dimension axis title", - ref: "props.axisTitleD", - defaultValue: "Dimension axis", - expression: "optional", - show: function(data) { - return (data.qHyperCubeDef.qDimensions.length == 0 - && (data.props.labelTitleD == 'B' || data.props.labelTitleD == 'T')); - } - }, - LabelStyleD: { - type: "string", - component: "dropdown", - label: "Label style", - ref: "props.labelStyleD", - defaultValue: "A", - options: [ - { value: "A", label: "Auto"}, - { value: "H", label: "Horizontal"}, - { value: "S", label: "Staggered"}, - { value: "T", label: "Tilted"} - ], - show: function(data) { - return data.props.labelTitleD != 'N' && data.props.labelTitleD != 'T'; - } - }, - gridlinesD: { - type: "boolean", - component: "switch", - label: "Gridlines", - ref: "props.gridlinesD", - defaultValue: false, - options: [ - { value: true, label: "Gridlines"}, - { value: false, label: "No gridlines"} - ], - show: function(data) { - return data.props.labelTitleD != 'N' && data.props.labelTitleD != 'T'; - } - }, - axisMarginD: { - type: "string", - component: "dropdown", - label: "Dimension margin size", - ref: "props.axisMarginD", - defaultValue: "M", - options: [ - { value: "N", label: "Narrow"}, - { value: "M", label: "Medium"}, - { value: "W", label: "Wide"} - ], - show: function(data) { - return data.props.labelTitleD != 'N' && data.props.labelTitleD != 'T'; - } - } - } - }, - measureAxis: { - type: "items", - label: function(data) { - var t = ""; - if (data.qHyperCubeDef.qDimensions.length == 0 || data.qHyperCubeDef.qMeasures.length > 1) { - if (data.props.axisTitleM.length > 0) - t = " (" + data.props.axisTitleM + ")" - } - else if (data.qHyperCubeDef.qMeasures.length > 0) { - if (data.qHyperCubeDef.qMeasures[0].qDef.hasOwnProperty("qLabel")) { - t = data.qHyperCubeDef.qMeasures[0].qDef.qLabel; - } - else { - t = data.qHyperCubeDef.qMeasures[0].qDef.qDef; - } - t = " (" + (t.startsWith("=") ? t.slice(1) : t) + ")"; - }; - return (data.props.orientation == "V" ? "Y" : "X") + "-Axis" + t; - }, - items: { - labelTitleM: { - type: "string", - component: "dropdown", - label: "Labels and title", - ref: "props.labelTitleM", - defaultValue: "B", - options: [ - { value: "B", label: "Labels and title"}, - { value: "L", label: "Labels only"}, - { value: "T", label: "Titles only"}, - { value: "N", label: "None"} - ] - }, - axisTitleM: { - type: "string", - label: "Measure axis title", - ref: "props.axisTitleM", - defaultValue: "Measure axis", - expression: "optional", - show: function(data) { - return (( - data.qHyperCubeDef.qDimensions.length == 0 - || ( - data.qHyperCubeDef.qDimensions.length == 1 - && data.qHyperCubeDef.qMeasures.length > 1 - ) - ) - && (data.props.labelTitleM == 'B' || data.props.labelTitleM == 'T')); - } - }, - LabelStyleM: { - type: "string", - component: "dropdown", - label: "Label style", - ref: "props.labelStyleM", - defaultValue: "A", - options: [ - { value: "A", label: "Auto"}, - { value: "H", label: "Horizontal"}, - { value: "S", label: "Staggered"}, - { value: "T", label: "Tilted"} - ], - show: function(data) { - return data.props.labelTitleM != 'N' && data.props.labelTitleM != 'T'; - } - }, - gridlinesM: { - type: "boolean", - component: "switch", - label: "Gridlines", - ref: "props.gridlinesM", - defaultValue: true, - options: [ - { value: true, label: "Gridlines"}, - { value: false, label: "No gridlines"} - ], - show: function(data) { - return data.props.labelTitleM != 'N' && data.props.labelTitleM != 'T'; - } - }, - axisMarginM: { - type: "string", - component: "dropdown", - label: "Measure margin size", - ref: "props.axisMarginM", - defaultValue: "M", - options: [ - { value: "N", label: "Narrow"}, - { value: "M", label: "Medium"}, - { value: "W", label: "Wide"} - ], - show: function(data) { - return data.props.labelTitleM != 'N' && data.props.labelTitleM != 'T'; - } - }, - ticks: { - type: "string", - component: "dropdown", - label: "Measure Tick spacing", - ref: "props.ticks", - defaultValue: "5", - options: [ - { value: "5", label: "Wide"}, - { value: "10", label: "Medium"}, - { value: "20", label: "Narrow"} - ], - show: function(data) { - return data.props.labelTitleM != 'N' && data.props.labelTitleM != 'T'; - } - }, - axisFormatM: { - type: "string", - component: "dropdown", - label: "Measure axis number format", - ref: "props.axisFormatM", - defaultValue: "S", - options: [ - { value: "A", label: "Auto"}, - { value: "N", label: "Number"}, - { value: "P", label: "Percent"}, - { value: "S", label: "SI-notation (K, M, etc.)"}, - { value: "C", label: "Custom"} - ], - show: function(data) { - return data.props.labelTitleM != 'N' && data.props.labelTitleM != 'T'; - } - }, - axisFormatMs: { - type: "string", - label: "Measure axis number format string", - ref: "props.axisFormatMs", - defaultValue: "s", - expression: "optional", - show: function(data) { - return data.props.axisFormatM == "C"; - } - }, - axisFormatMURL: { - type: "string", - component: "link", - label: "D3 format strings", - url: "https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md" - } - } - }, - barText: { - type: "items", - label: "Text on bars", - items: { - showTexts: { - type: "string", - component: "dropdown", - label: "Text on bars", - ref: "props.showTexts", - defaultValue: "N", - options: [ - { value: "N", label: "None"}, - { value: "B", label: "Inside Bars"}, - { value: "T", label: "Total"}, - { value: "A", label: "Both"} - ] - }, - showDim: { - type: "string", - component: "dropdown", - label: "Text to show in bars", - ref: "props.showDim", - defaultValue: "M", - options: [ - { value: "M", label: "Measure"}, - { value: "D", label: "Dimension"}, - { value: "P", label: "Percent"} - ], - show: function(data) { - return ~"BA".indexOf(data.props.showTexts) && data.props.normalized; - } - }, - showDim2: { - type: "string", - component: "dropdown", - label: "Text to show in bars", - ref: "props.showDim", - defaultValue: "M", - options: [ - { value: "M", label: "Measure"}, - { value: "D", label: "Dimension"} - ], - show: function(data) { - return ~"BA".indexOf(data.props.showTexts) && !data.props.normalized; - } - }, - showTot: { - type: "string", - component: "dropdown", - label: "Text to show for total", - ref: "props.showTot", - defaultValue: "M", - options: [ - { value: "M", label: "Measure"}, - { value: "D", label: "Dimension"} - ], - show: function(data) { - return ~"TA".indexOf(data.props.showTexts); - } - }, - innerBarPadH: { - type: "number", - label: "Horizontal inner bar padding (px)", - ref: "props.innerBarPadH", - defaultValue: 2, - expression: "optional", - show: function(data) { - return data.props.showTexts != "N"; - } - }, - innerBarPadV: { - type: "number", - label: "Vertical inner bar padding (px)", - ref: "props.innerBarPadV", - defaultValue: 2, - expression: "optional", - show: function(data) { - return data.props.showTexts != "N"; - } - }, - textSizeAbs: { - type: "boolean", - component: "switch", - label: "Text size proportional", - ref: "props.textSizeAbs", - defaultValue: true, - options: [ - { value: true, label: "Not proportional"}, - { value: false, label: "Proportional"} - ], - show: function(data) { - return data.props.showTexts != "N" - } - }, - textSizeFactor: { - type: "number", - label: "Text size proportion factor", - ref: "props.textSizeFactor", - defaultValue: 1, - show: function(data) { - return data.props.showTexts != "N" && !data.props.textSizeAbs; - } - }, - textSize: { - type: "number", - label: "Text size (px)", - ref: "props.textSize", - defaultValue: 10, - expression: "optional", - show: function(data) { - return data.props.showTexts != "N" && data.props.textSizeAbs; - } - }, - textSizeMax: { - type: "number", - label: "Max text size (px)", - ref: "props.textSize", - defaultValue: 18, - expression: "optional", - show: function(data) { - return data.props.showTexts != "N" && !data.props.textSizeAbs; - } - }, - textDots: { - type: "boolean", - component: "switch", - label: "Show text at all if ellipsis", - ref: "props.textDots", - defaultValue: true, - options: [ - { value: true, label: "Show"}, - { value: false, label: "Don't show"} - ], - show: function(data) { - return data.props.showTexts != "N"; - } - }, - textColor: { - type: "string", - component: "dropdown", - label: "Text color", - ref: "props.textColor", - defaultValue: "Auto", - options: [ - { value: "Auto", label: "Auto"}, - { value: "Black", label: "Black"}, - { value: "White", label: "White"} - ], - show: function(data) { - return data.props.showTexts != "N"; - } - }, - vAlign: { - type: "string", - component: "dropdown", - label: "Vertical alignment", - ref: "props.vAlign", - defaultValue: "C", - options: [ - { value: "C", label: "Center"}, - { value: "T", label: "Top"}, - { value: "B", label: "Bottom"} - ], - show: function(data) { - return data.props.showTexts != "N"; - } - }, - hAlign: { - type: "string", - component: "dropdown", - label: "Horizontal alignment", - ref: "props.hAlign", - defaultValue: "C", - options: [ - { value: "C", label: "Center"}, - { value: "L", label: "Left"}, - { value: "R", label: "Right"} - ], - show: function(data) { - return data.props.showTexts != "N"; - } - }, - totalFormatM: { - type: "string", - component: "dropdown", - label: "Total format", - ref: "props.totalFormatM", - defaultValue: "S", - options: [ - { value: "N", label: "Number"}, - { value: "P", label: "Percent"}, - { value: "S", label: "SI-notation (K, M, etc.)"}, - { value: "C", label: "Custom"} - ], - show: function(data) { - return data.props.showTexts != "N"; - } - }, - totalFormatMs: { - type: "string", - label: "Total format string", - ref: "props.totalFormatMs", - defaultValue: ",.3s", - expression: "optional", - show: function(data) { - return data.props.showTexts != "N" && data.props.totalFormatM == "C"; - } - }, - totalFormatMURL: { - type: "string", - component: "link", - label: "D3 format strings", - url: "https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md", - show: function(data) { - return data.props.showTexts != "N" && data.props.totalFormatM == "C"; - } - } - } - }, - transitions: { - type: "items", - label: "Transitions", - items: { - transitions: { - type: "boolean", - component: "switch", - label: "Transitions", - ref: "props.transitions", - defaultValue: true, - options: [ - { value: true, label: "Transitions enabled"}, - { value: false, label: "Transitions disabled"} - ] - }, - transitionDelay: { - type: "string", - label: "Transition delay", - ref: "props.transitionDelay", - defaultValue: "500", - expression: "optional", - show: function(data) { - return data.props.transitions; - } - }, - transitionDuration: { - type: "string", - label: "Transition duration", - ref: "props.transitionDuration", - defaultValue: "1000", - expression: "optional", - show: function(data) { - return data.props.transitions; - } - }, - transitionStyle: { - type: "string", - component: "dropdown", - label: "Transition style", - ref: "props.ease", - defaultValue: "linear", - options: [ - { value: "linear", label: "Linear"}, - { value: "quad", label: "Quad"}, - { value: "cubic", label: "Cubic"}, - { value: "sin", label: "Sine"}, - { value: "exp", label: "Exponential"}, - { value: "circle", label: "Circle"}, - { value: "elastic", label: "Elastic"}, - { value: "back", label: "Back"}, - { value: "bounce", label: "Bounce"} - ], - show: function(data) { - return data.props.transitions; - } - } - } - } - } - } - return { - type: "items", - component: "accordion", - items: { - dimensions: dimensions, - measures: measures, - sorting: sorting, - addons: addons, - appearance: appearance - } - }; - -} ); diff --git a/barsPlus-template.html b/barsPlus-template.html deleted file mode 100644 index e2a47dd..0000000 --- a/barsPlus-template.html +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/package.json b/package.json new file mode 100644 index 0000000..0322f58 --- /dev/null +++ b/package.json @@ -0,0 +1,3 @@ +{ + "name": "barsPlus" +} \ No newline at end of file diff --git a/barsPlus-directive.js b/src/barsPlus-directive.js similarity index 95% rename from barsPlus-directive.js rename to src/barsPlus-directive.js index e4010ea..23d7bf0 100644 --- a/barsPlus-directive.js +++ b/src/barsPlus-directive.js @@ -1,199 +1,204 @@ -/** - * barsPlus AngularJS Directive - * - * Define AngularJS directive - * Sets up properties of object ldwBarsPlus (variable 'g') and defines watches - * - * Author: L. Woodside - * Modification History: - * - * Version Person Date Description - * V1.0.0 L. Woodside 19-Dec-2016 Initial Release - * V1.1.0 L. Woodside 29-Dec-2016 Added text on bars - * V1.2.0 L. Woodside 07-Jan-2017 Allow multiple measures - * V1.3.0 L. Woodside 15-Jan-2017 Improved color options - * -*/ -define( [ - "jquery", - "qvangular", - "./ldw-barsPlus", - "css!./barsPlus.css", - "./lib/d3.v3" -], - -function ($, qvangular, ldwBarsPlus) { - 'use strict'; - - qvangular.directive("barsPlus", [ - function() { - return { - restrict: "EA", - link: function($scope, $element, $attrs) { - - $scope.g = Object.create(ldwbarsPlus); - - var g = $scope.g; - - // package parameters so bar chart js can be tested in standalone HTML - // with different input data structure - $scope.initProps = function() { - var g = $scope.g, l = $scope.layout, p = $scope.layout.props; - - g.id = l.qInfo.qId; - g.inSelections = false; - g.editMode = false; - g.selectionMode = l.selectionMode; - - // number of defined dimensions and measures - g.defDims = l.qHyperCube.qDimensionInfo.length; - g.defMeas = l.qHyperCube.qMeasureInfo.length; - - if (g.defDims == 0) - g.axisTitleD = p.axisTitleD; - else - g.axisTitleD = l.qHyperCube.qDimensionInfo[0].qFallbackTitle; - - if (g.defDims == 0 || (g.defDims == 1 && g.defMeas > 1)) { - g.measures = []; - for (var i = 0; i < g.defMeas; i++) { - var t = l.qHyperCube.qMeasureInfo[i].qFallbackTitle; - g.measures.push(t.startsWith("=") ? t.slice(1) : t); - } - g.axisTitleM = p.axisTitleM; - } - else - g.axisTitleM = l.qHyperCube.qMeasureInfo[0].qFallbackTitle; - - // Presentation - g.orientation = p.orientation; - g.normalized = p.normalized; - g.showDeltas = p.showDeltas - g.barGap = p.barSpacing; - g.outerGap = p.outerGap - g.gridHeight = p.gridHeight; - g.backgroundColor = p.backgroundColor; - - // Colors and Legend - g.colorSource = p.colorSource; - g.colorAttr = p.colorAttr; - g.colorScheme = p.colorScheme; - g.colorOffset = p.colorOffset; - g.singleColor = p.singleColor; - g.showLegend = p.showLegend; - g.legendPosition = p.legendPosition; - g.legendSize = p.legendSize; - g.legendSpacing = p.legendSpacing; - - // Dimension Axis - g.labelTitleD = p.labelTitleD; - g.labelStyleD = p.labelStyleD; - g.gridlinesD = p.gridlinesD; - g.axisMarginD = p.axisMarginD; - - // Measure Axis - g.labelTitleM = p.labelTitleM; - g.labelStyleM = p.labelStyleM; - g.gridlinesM = p.gridlinesM; - g.axisMarginM = p.axisMarginM; - g.ticks = p.ticks; - g.axisFormatM = p.axisFormatM; - g.axisFormatMs = p.axisFormatMs; - - // Text on bars - - g.showTexts = p.showTexts; - g.showDim = p.showDim; - g.showTot = p.showTot; - g.innerBarPadH = p.innerBarPadH; - g.innerBarPadV = p.innerBarPadV; - g.textSizeAbs = p.textSizeAbs; - g.textSizeFactor = p.textSizeFactor; - g.textSize = p.textSize; - g.textDots = p.textDots; - g.textColor = p.textColor; - g.vAlign = p.vAlign; - g.hAlign = p.hAlign; - g.totalFormatM = p.totalFormatM; - g.totalFormatMs = p.totalFormatMs; - - // Transitions - g.transitions = p.transitions; - g.transitionDelay = p.transitionDelay; - g.transitionDuration = p.transitionDuration; - g.ease = p.ease - - }; - - $scope.initProps(); - - g.component = d3.select($element[0]).attr("id",g.id) - - g.width = $element.parent().width(); - g.height = $element.parent().height(); - g.rawData = $scope.layout.qHyperCube.qDataPages[0].qMatrix; - - g.initData(); - g.refreshChart(); - - // watch for when data changes - $scope.$watch(function() { return $scope.layout.qHyperCube.qDataPages[0].qMatrix; } - ,function(newValue, oldValue) { - if (newValue != oldValue) { - g.rawData = newValue; - g.initData(); - g.updateBars(); - } - }); - // watch for when chart is resized - $scope.$watch( - function () { - return $element.parent().width() + "," + $element.parent().height(); - } - ,function(newValue, oldValue) { - if (newValue != oldValue) { - - var t = newValue.split(","); - g.width = t[0]; - g.height = t[1]; - - g.refreshChart(); - } - } - ); - // watch for selection count going to zero in standard selection mode - // to partially address QS bug where clear selections button does not reset element classes - // this does not completely work if there were existing selections on field prior to entering - // selection mode -// $scope.$watch( -// function() { -// return $scope.layout.qHyperCube.qDimensionInfo[0].qStateCounts.qSelected; -// } -// ,function(newValue, oldValue) { -// -// if (newValue != oldValue && newValue == 0) { -// d3.selectAll(".selected").classed("selected",false); -// } -// } -// ); - // watch for when selection mode enabled - $scope.$watch( - function() { - return $scope.layout.qSelectionInfo.qInSelections; - } - ,function(newValue, oldValue) { - if (newValue != oldValue) { - - g.inSelections = newValue || false; - if (!g.inSelections) { - d3.selectAll(".selected").classed("selected",false); - } - } - } - ); - } - } - } - ]); - -}); +/** + * barsPlus AngularJS Directive + * + * Define AngularJS directive + * Sets up properties of object ldwBarsPlus (variable 'g') and defines watches + * + * Author: L. Woodside + * Modification History: + * + * Version Person Date Description + * V1.0.0 L. Woodside 19-Dec-2016 Initial Release + * V1.1.0 L. Woodside 29-Dec-2016 Added text on bars + * V1.2.0 L. Woodside 07-Jan-2017 Allow multiple measures + * V1.3.0 L. Woodside 15-Jan-2017 Improved color options + * +*/ +define( [ + "jquery", + "qvangular", + "./ldw-barsPlus", + "css!./barsPlus.css", + "./lib/d3.v3" +], + +function ($, qvangular, ldwBarsPlus) { + 'use strict'; + + qvangular.directive("barsPlus", [ + function() { + return { + restrict: "EA", + link: function($scope, $element, $attrs) { + + $scope.g = Object.create(ldwbarsPlus); + + var g = $scope.g; + + // package parameters so bar chart js can be tested in standalone HTML + // with different input data structure + $scope.initProps = function() { + var g = $scope.g, l = $scope.layout, p = $scope.layout.props; + + g.id = l.qInfo.qId; + g.inSelections = false; + g.editMode = false; + g.selectionMode = l.selectionMode; + + // number of defined dimensions and measures + g.defDims = l.qHyperCube.qDimensionInfo.length; + g.defMeas = l.qHyperCube.qMeasureInfo.length; + + if (g.defDims == 0) + g.axisTitleD = p.axisTitleD; + else + g.axisTitleD = l.qHyperCube.qDimensionInfo[0].qFallbackTitle; + + if (g.defDims == 0 || (g.defDims == 1 && g.defMeas > 1)) { + g.measures = []; + for (var i = 0; i < g.defMeas; i++) { + var t = l.qHyperCube.qMeasureInfo[i].qFallbackTitle; + g.measures.push(t.startsWith("=") ? t.slice(1) : t); + } + g.axisTitleM = p.axisTitleM; + } + else + g.axisTitleM = l.qHyperCube.qMeasureInfo[0].qFallbackTitle; + + // Presentation + g.orientation = p.orientation; + g.normalized = p.normalized; + g.showDeltas = p.showDeltas + g.barGap = p.barSpacing; + g.outerGap = p.outerGap + g.gridHeight = p.gridHeight; + g.backgroundColor = p.backgroundColor; + + // Colors and Legend + g.colorSource = p.colorSource; + g.colorAttr = p.colorAttr; + g.colorScheme = p.colorScheme; + g.colorOffset = p.colorOffset; + g.singleColor = p.singleColor; + g.showLegend = p.showLegend; + g.legendPosition = p.legendPosition; + g.legendSize = p.legendSize; + g.legendSpacing = p.legendSpacing; + + // Dimension Axis + g.labelTitleD = p.labelTitleD; + g.labelStyleD = p.labelStyleD; + g.gridlinesD = p.gridlinesD; + g.axisMarginD = p.axisMarginD; + + // Measure Axis + g.labelTitleM = p.labelTitleM; + g.labelStyleM = p.labelStyleM; + g.gridlinesM = p.gridlinesM; + g.axisMarginM = p.axisMarginM; + g.ticks = p.ticks; + g.axisFormatM = p.axisFormatM; + g.axisFormatMs = p.axisFormatMs; + + // Text on bars + + g.showTexts = p.showTexts; + g.showDim = p.showDim; + g.showTot = p.showTot; + g.innerBarPadH = p.innerBarPadH; + g.innerBarPadV = p.innerBarPadV; + g.textSizeAbs = p.textSizeAbs; + g.textSizeFactor = p.textSizeFactor; + g.textSize = p.textSize; + g.textDots = p.textDots; + g.textColor = p.textColor; + g.vAlign = p.vAlign; + g.hAlign = p.hAlign; + g.totalFormatM = p.totalFormatM; + g.totalFormatMs = p.totalFormatMs; + + // Transitions + var inPrinting = typeof window.testprint === "object"; + if (!inPrinting) { + g.transitions = p.transitions; + } else { + g.transitions = false; + } + g.transitionDelay = p.transitionDelay; + g.transitionDuration = p.transitionDuration; + g.ease = p.ease + + }; + + $scope.initProps(); + + g.component = d3.select($element[0]).attr("id",g.id) + + g.width = $element.parent().width(); + g.height = $element.parent().height(); + g.rawData = $scope.layout.qHyperCube.qDataPages[0].qMatrix; + + g.initData(); + g.refreshChart(); + + // watch for when data changes + $scope.$watch(function() { return $scope.layout.qHyperCube.qDataPages[0].qMatrix; } + ,function(newValue, oldValue) { + if (newValue != oldValue) { + g.rawData = newValue; + g.initData(); + g.updateBars(); + } + }); + // watch for when chart is resized + $scope.$watch( + function () { + return $element.parent().width() + "," + $element.parent().height(); + } + ,function(newValue, oldValue) { + if (newValue != oldValue) { + + var t = newValue.split(","); + g.width = t[0]; + g.height = t[1]; + + g.refreshChart(); + } + } + ); + // watch for selection count going to zero in standard selection mode + // to partially address QS bug where clear selections button does not reset element classes + // this does not completely work if there were existing selections on field prior to entering + // selection mode +// $scope.$watch( +// function() { +// return $scope.layout.qHyperCube.qDimensionInfo[0].qStateCounts.qSelected; +// } +// ,function(newValue, oldValue) { +// +// if (newValue != oldValue && newValue == 0) { +// d3.selectAll(".selected").classed("selected",false); +// } +// } +// ); + // watch for when selection mode enabled + $scope.$watch( + function() { + return $scope.layout.qSelectionInfo.qInSelections; + } + ,function(newValue, oldValue) { + if (newValue != oldValue) { + + g.inSelections = newValue || false; + if (!g.inSelections) { + d3.selectAll(".selected").classed("selected",false); + } + } + } + ); + } + } + } + ]); + +}); diff --git a/barsPlus-initprops.js b/src/barsPlus-initprops.js similarity index 100% rename from barsPlus-initprops.js rename to src/barsPlus-initprops.js diff --git a/src/barsPlus-props.js b/src/barsPlus-props.js new file mode 100644 index 0000000..56bab2e --- /dev/null +++ b/src/barsPlus-props.js @@ -0,0 +1,937 @@ +define([], + /** + * barsPlus Properties - set up appearance of accordion properties panel in Qlik Sense + * + * Defaults are defined here. + * Settings modify objects in object: layout.props + * + * Author: L. Woodside + * Modification History: + * + * Version Person Date Description + * V1.0.0 L. Woodside 19-Dec-2016 Initial Release + * V1.1.0 L. Woodside 29-Dec-2016 Added text on bars + * V1.2.0 L. Woodside 07-Jan-2017 Allow multiple measures + * V1.3.0 L. Woodside 15-Jan-2017 Improved color options + * V1.3.1 L. Woodside 27-Jan-2017 Fix problem with legend properties + * + */ + function () { + 'use strict'; + + var param = { + "qInfo": { + "qType": "DimensionList" + }, + "qDimensionListDef": { + "qType": "dimension", + "qData": { + "title": "/title" + } + } + } + let dimensionList = [] + // window.engineModuleGlobal.currentApp.createSessionObject(param) + // .then(function(object) { + // return object.getLayout() + // }) + // .then(function(layout) { + // for (const dimension of layout.qDimensionList.qItems) { + // dimensionList.push({id: dimension.qInfo.qId, title: dimension.qMeta.title}) + // } + // }) + // .catch(function(error) { + // console.log(error) + // }) + + function getDimensionName(id) { + return dimensionList.filter(function (value) { + if (value.id === id) { + return value + } + })[0].title; + } + + var dimensions = { + uses: "dimensions", + min: 0, + max: 2, + items: { + attribute: { + type: "string", + component: "expression", + ref: "qAttributeExpressions.0.qExpression", + label: "Dimension attribute", + defaultValue: "" + } + } + }; + var measures = { + uses: "measures", + min: 1, + max: 10, + items: { + attribute: { + type: "string", + component: "expression", + ref: "qAttributeExpressions.0.qExpression", + label: "Measure attribute", + defaultValue: "" + } + } + }; + var sorting = { + uses: "sorting" + }; + var addons = { + uses: "addons" + , items: { + dataHandling: { + uses: "dataHandling" + }, + selectionMode: { + type: "string", + component: "dropdown", + label: "Selection mode", + ref: "selectionMode", + defaultValue: "CONFIRM", + options: [ + { value: "CONFIRM", label: "Standard" }, + { value: "QUICK", label: "Quick" } + ] + } + } + }; + + var appearance = { + uses: "settings", + items: { + presentation: { + type: "items", + label: "Presentation", + items: { + orientation: { + type: "string", + component: "dropdown", + label: "Orientation", + ref: "props.orientation", + defaultValue: "V", + options: [ + { value: "V", label: "Vertical bars" }, + { value: "H", label: "Horizontal bars" }, + ] + }, + normalized: { + type: "boolean", + component: "switch", + label: "100% bar chart", + ref: "props.normalized", + defaultValue: false, + options: [ + { value: false, label: "Not 100%" }, + { value: true, label: "100% bars" } + ], + show: function (data) { + return (data.qHyperCubeDef.qDimensions.length > 1 + || (data.qHyperCubeDef.qDimensions.length == 1 + && data.qHyperCubeDef.qMeasures.length > 1 + )); + } + }, + showDeltas: { + type: "boolean", + component: "switch", + label: "Show bar connectors", + ref: "props.showDeltas", + defaultValue: false, + options: [ + { value: false, label: "Standard bars" }, + { value: true, label: "Bars with connectors" } + ], + show: function (data) { + return (data.qHyperCubeDef.qDimensions.length > 1 + || (data.qHyperCubeDef.qDimensions.length == 1 + && data.qHyperCubeDef.qMeasures.length > 1 + )); + } + }, + barSpacing: { + type: "number", + label: "Bar spacing", + ref: "props.barSpacing", + defaultValue: 0.3, + expression: "optional" + }, + barSpacing2: { + type: "number", + component: "slider", + ref: "props.barSpacing", + defaultValue: 0.3, + min: 0, + max: 1.001, // Excludes 1 otherwise + step: 0.1 + }, + outerGap: { + type: "number", + label: "Outer bar spacing", + ref: "props.outerGap", + defaultValue: 0.1, + expression: "optional" + }, + outerGap2: { + type: "number", + component: "slider", + ref: "props.outerGap", + defaultValue: 0.1, + min: 0, + max: 1.001, // Excludes 1 otherwise + step: 0.1 + }, + gridHeight: { + type: "number", + label: "Grid height relative to max bar", + ref: "props.gridHeight", + // defaultValue: 1.1, + expression: "optional" + }, + gridHeight2: { + type: "number", + component: "slider", + // label: "Grid height relative to max", + ref: "props.gridHeight", + defaultValue: 1.1, + min: 1, + max: 2.001, + step: 0.1 + }, + backgroundColor: { + type: "string", + label: "Background color", + ref: "props.backgroundColor", + defaultValue: "white", + expression: "optional" + }, + } + }, + colors: { + type: "items", + label: "Colors and Legend", + items: { + colorSource: { + type: "string", + component: "dropdown", + label: "Color source", + ref: "props.colorSource", + defaultValue: "A", + options: [ + { value: "A", label: "Assigned" }, + { value: "C", label: "Calculated" } + ] + }, + colorAttr: { + type: "string", + component: "dropdown", + label: "Color attribute", + ref: "props.colorAttr", + defaultValue: "O", + options: [ + { value: "O", label: "Attribute is offset in scheme" }, + { value: "C", label: "Attribute is color value" } + ], + show: function (data) { + return data.props.colorSource == "C"; + } + }, + colorScheme: { + type: "string", + component: "dropdown", + label: "Color scheme", + ref: "props.colorScheme", + defaultValue: "category20b", + options: [ + { value: "category20b", label: "category20b" }, + { value: "category20c", label: "category20c" }, + { value: "category20", label: "catagory20" }, + { value: "category10", label: "category10" }, + { value: "google20", label: "google20" }, + { value: "custom100", label: "custom100" }, + { value: "qlikView18", label: "qlikView18" }, + { value: "qlikSense12", label: "qlikSense12" } + ], + show: function (data) { + return (data.props.colorSource != "C" + || (data.props.colorSource == "C" && data.props.colorAttr == "O")); + } + }, + colorOffset: { + type: "number", + label: "Start offset in color scheme", + ref: "props.colorOffset", + defaultValue: 0, + expression: "optional", + show: function (data) { + return data.props.colorSource != "C"; + } + }, + singleColor: { + type: "boolean", + component: "switch", + label: "Single Color", + ref: "props.singleColor", + defaultValue: true, + options: [ + { value: true, label: "Single Color" }, + { value: false, label: "Multi-color" } + ], + show: function (data) { + return ((data.qHyperCubeDef.qDimensions.length == 0 + || (data.qHyperCubeDef.qDimensions.length == 1 + && data.qHyperCubeDef.qMeasures.length == 1 + )) && data.props.colorSource != "C"); + } + }, + showLegend: { + type: "boolean", + component: "switch", + label: "Show legend", + ref: "props.showLegend", + defaultValue: false, + options: [ + { value: false, label: "No legend" }, + { value: true, label: "Show legend" } + ], + show: function (data) { + return ( + ( + ( + (data.qHyperCubeDef.qDimensions.length == 0 && data.qHyperCubeDef.qMeasures.length > 1) + || (data.qHyperCubeDef.qDimensions.length == 1 && data.qHyperCubeDef.qMeasures.length == 1) + ) + && ( + (data.props.colorSource != "C" && !data.props.singleColor) + || data.props.colorSource == "C" + ) + ) + || data.qHyperCubeDef.qDimensions.length > 1 + || (data.qHyperCubeDef.qDimensions.length == 1 && data.qHyperCubeDef.qMeasures.length > 1) + ); + } + }, + legendPosition: { + type: "string", + component: "dropdown", + label: "Legend position", + ref: "props.legendPosition", + defaultValue: "R", + options: [ + { value: "R", label: "Right" }, + { value: "L", label: "Left" }, + { value: "T", label: "Top" }, + { value: "B", label: "Bottom" } + ], + show: function (data) { + return data.props.showLegend && ( + ( + ( + (data.qHyperCubeDef.qDimensions.length == 0 && data.qHyperCubeDef.qMeasures.length > 1) + || (data.qHyperCubeDef.qDimensions.length == 1 && data.qHyperCubeDef.qMeasures.length == 1) + ) + && ( + (data.props.colorSource != "C" && !data.props.singleColor) + || data.props.colorSource == "C" + ) + ) + || data.qHyperCubeDef.qDimensions.length > 1 + || (data.qHyperCubeDef.qDimensions.length == 1 && data.qHyperCubeDef.qMeasures.length > 1) + ); + } + }, + legendSize: { + type: "string", + component: "dropdown", + label: "Legend size", + ref: "props.legendSize", + defaultValue: "M", + options: [ + { value: "N", label: "Narrow" }, + { value: "M", label: "Medium" }, + { value: "W", label: "Wide" } + ], + show: function (data) { + return data.props.showLegend && ( + ( + ( + (data.qHyperCubeDef.qDimensions.length == 0 && data.qHyperCubeDef.qMeasures.length > 1) + || (data.qHyperCubeDef.qDimensions.length == 1 && data.qHyperCubeDef.qMeasures.length == 1) + ) + && ( + (data.props.colorSource != "C" && !data.props.singleColor) + || data.props.colorSource == "C" + ) + ) + || data.qHyperCubeDef.qDimensions.length > 1 + || (data.qHyperCubeDef.qDimensions.length == 1 && data.qHyperCubeDef.qMeasures.length > 1) + ); + } + }, + legendSpacing: { + type: "string", + component: "dropdown", + label: "Legend item spacing", + ref: "props.legendSpacing", + defaultValue: "M", + options: [ + { value: "N", label: "Narrow" }, + { value: "M", label: "Medium" }, + { value: "W", label: "Wide" } + ], + show: function (data) { + return data.props.showLegend && (data.props.legendPosition == "T" || data.props.legendPosition == "B") && ( + ( + ( + (data.qHyperCubeDef.qDimensions.length == 0 && data.qHyperCubeDef.qMeasures.length > 1) + || (data.qHyperCubeDef.qDimensions.length == 1 && data.qHyperCubeDef.qMeasures.length == 1) + ) + && ( + (data.props.colorSource != "C" && !data.props.singleColor) + || data.props.colorSource == "C" + ) + ) + || data.qHyperCubeDef.qDimensions.length > 1 + || (data.qHyperCubeDef.qDimensions.length == 1 && data.qHyperCubeDef.qMeasures.length > 1) + ); + } + } + } + }, + dimensionAxis: { + type: "items", + label: function (data) { + var t = ""; + if (data.qHyperCubeDef.qDimensions.length == 0) { + if (data.props.axisTitleD.length > 0) + t = " (" + data.props.axisTitleD + ")" + } + else { + if (typeof data.qHyperCubeDef.qDimensions[0].qLibraryId !== "undefined") { + var libraryId = data.qHyperCubeDef.qDimensions[0].qLibraryId; + t = getDimensionName(libraryId); + } else { + t = data.qHyperCubeDef.qDimensions[0].qDef.qFieldLabels[0]; + if (t.length == 0) { + t = data.qHyperCubeDef.qDimensions[0].qDef.qFieldDefs[0]; + } + } + t = " (" + (t.startsWith("=") ? t.slice(1) : t) + ")"; + }; + return (data.props.orientation == "V" ? "X" : "Y") + "-Axis" + t; + }, + items: { + LabelTitleD: { + type: "string", + component: "dropdown", + label: "Labels and title", + ref: "props.labelTitleD", + defaultValue: "B", + options: [ + { value: "B", label: "Labels and title" }, + { value: "L", label: "Labels only" }, + { value: "T", label: "Titles only" }, + { value: "N", label: "None" } + ] + }, + axisTitleD: { + type: "string", + label: "Dimension axis title", + ref: "props.axisTitleD", + defaultValue: "Dimension axis", + expression: "optional", + show: function (data) { + return (data.qHyperCubeDef.qDimensions.length == 0 + && (data.props.labelTitleD == 'B' || data.props.labelTitleD == 'T')); + } + }, + LabelStyleD: { + type: "string", + component: "dropdown", + label: "Label style", + ref: "props.labelStyleD", + defaultValue: "A", + options: [ + { value: "A", label: "Auto" }, + { value: "H", label: "Horizontal" }, + { value: "S", label: "Staggered" }, + { value: "T", label: "Tilted" } + ], + show: function (data) { + return data.props.labelTitleD != 'N' && data.props.labelTitleD != 'T'; + } + }, + gridlinesD: { + type: "boolean", + component: "switch", + label: "Gridlines", + ref: "props.gridlinesD", + defaultValue: false, + options: [ + { value: true, label: "Gridlines" }, + { value: false, label: "No gridlines" } + ], + show: function (data) { + return data.props.labelTitleD != 'N' && data.props.labelTitleD != 'T'; + } + }, + axisMarginD: { + type: "string", + component: "dropdown", + label: "Dimension margin size", + ref: "props.axisMarginD", + defaultValue: "M", + options: [ + { value: "N", label: "Narrow" }, + { value: "M", label: "Medium" }, + { value: "W", label: "Wide" } + ], + show: function (data) { + return data.props.labelTitleD != 'N' && data.props.labelTitleD != 'T'; + } + } + } + }, + measureAxis: { + type: "items", + label: function (data) { + var t = ""; + if (data.qHyperCubeDef.qDimensions.length == 0 || data.qHyperCubeDef.qMeasures.length > 1) { + if (data.props.axisTitleM.length > 0) + t = " (" + data.props.axisTitleM + ")" + } + else if (data.qHyperCubeDef.qMeasures.length > 0) { + if (data.qHyperCubeDef.qMeasures[0].qDef.hasOwnProperty("qLabel")) { + t = data.qHyperCubeDef.qMeasures[0].qDef.qLabel; + } + else { + t = data.qHyperCubeDef.qMeasures[0].qDef.qDef; + } + t = " (" + (t.startsWith("=") ? t.slice(1) : t) + ")"; + }; + return (data.props.orientation == "V" ? "Y" : "X") + "-Axis" + t; + }, + items: { + labelTitleM: { + type: "string", + component: "dropdown", + label: "Labels and title", + ref: "props.labelTitleM", + defaultValue: "B", + options: [ + { value: "B", label: "Labels and title" }, + { value: "L", label: "Labels only" }, + { value: "T", label: "Titles only" }, + { value: "N", label: "None" } + ] + }, + axisTitleM: { + type: "string", + label: "Measure axis title", + ref: "props.axisTitleM", + defaultValue: "Measure axis", + expression: "optional", + show: function (data) { + return (( + data.qHyperCubeDef.qDimensions.length == 0 + || ( + data.qHyperCubeDef.qDimensions.length == 1 + && data.qHyperCubeDef.qMeasures.length > 1 + ) + ) + && (data.props.labelTitleM == 'B' || data.props.labelTitleM == 'T')); + } + }, + LabelStyleM: { + type: "string", + component: "dropdown", + label: "Label style", + ref: "props.labelStyleM", + defaultValue: "A", + options: [ + { value: "A", label: "Auto" }, + { value: "H", label: "Horizontal" }, + { value: "S", label: "Staggered" }, + { value: "T", label: "Tilted" } + ], + show: function (data) { + return data.props.labelTitleM != 'N' && data.props.labelTitleM != 'T'; + } + }, + gridlinesM: { + type: "boolean", + component: "switch", + label: "Gridlines", + ref: "props.gridlinesM", + defaultValue: true, + options: [ + { value: true, label: "Gridlines" }, + { value: false, label: "No gridlines" } + ], + show: function (data) { + return data.props.labelTitleM != 'N' && data.props.labelTitleM != 'T'; + } + }, + axisMarginM: { + type: "string", + component: "dropdown", + label: "Measure margin size", + ref: "props.axisMarginM", + defaultValue: "M", + options: [ + { value: "N", label: "Narrow" }, + { value: "M", label: "Medium" }, + { value: "W", label: "Wide" } + ], + show: function (data) { + return data.props.labelTitleM != 'N' && data.props.labelTitleM != 'T'; + } + }, + ticks: { + type: "string", + component: "dropdown", + label: "Measure Tick spacing", + ref: "props.ticks", + defaultValue: "5", + options: [ + { value: "5", label: "Wide" }, + { value: "10", label: "Medium" }, + { value: "20", label: "Narrow" } + ], + show: function (data) { + return data.props.labelTitleM != 'N' && data.props.labelTitleM != 'T'; + } + }, + axisFormatM: { + type: "string", + component: "dropdown", + label: "Measure axis number format", + ref: "props.axisFormatM", + defaultValue: "S", + options: [ + { value: "A", label: "Auto" }, + { value: "N", label: "Number" }, + { value: "P", label: "Percent" }, + { value: "S", label: "SI-notation (K, M, etc.)" }, + { value: "C", label: "Custom" } + ], + show: function (data) { + return data.props.labelTitleM != 'N' && data.props.labelTitleM != 'T'; + } + }, + axisFormatMs: { + type: "string", + label: "Measure axis number format string", + ref: "props.axisFormatMs", + defaultValue: "s", + expression: "optional", + show: function (data) { + return data.props.axisFormatM == "C"; + } + }, + axisFormatMURL: { + type: "string", + component: "link", + label: "D3 format strings", + url: "https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md" + } + } + }, + barText: { + type: "items", + label: "Text on bars", + items: { + showTexts: { + type: "string", + component: "dropdown", + label: "Text on bars", + ref: "props.showTexts", + defaultValue: "N", + options: [ + { value: "N", label: "None" }, + { value: "B", label: "Inside Bars" }, + { value: "T", label: "Total" }, + { value: "A", label: "Both" } + ] + }, + showDim: { + type: "string", + component: "dropdown", + label: "Text to show in bars", + ref: "props.showDim", + defaultValue: "M", + options: [ + { value: "M", label: "Measure" }, + { value: "D", label: "Dimension" }, + { value: "P", label: "Percent" } + ], + show: function (data) { + return ~"BA".indexOf(data.props.showTexts) && data.props.normalized; + } + }, + showDim2: { + type: "string", + component: "dropdown", + label: "Text to show in bars", + ref: "props.showDim", + defaultValue: "M", + options: [ + { value: "M", label: "Measure" }, + { value: "D", label: "Dimension" } + ], + show: function (data) { + return ~"BA".indexOf(data.props.showTexts) && !data.props.normalized; + } + }, + showTot: { + type: "string", + component: "dropdown", + label: "Text to show for total", + ref: "props.showTot", + defaultValue: "M", + options: [ + { value: "M", label: "Measure" }, + { value: "D", label: "Dimension" } + ], + show: function (data) { + return ~"TA".indexOf(data.props.showTexts); + } + }, + innerBarPadH: { + type: "number", + label: "Horizontal inner bar padding (px)", + ref: "props.innerBarPadH", + defaultValue: 2, + expression: "optional", + show: function (data) { + return data.props.showTexts != "N"; + } + }, + innerBarPadV: { + type: "number", + label: "Vertical inner bar padding (px)", + ref: "props.innerBarPadV", + defaultValue: 2, + expression: "optional", + show: function (data) { + return data.props.showTexts != "N"; + } + }, + textSizeAbs: { + type: "boolean", + component: "switch", + label: "Text size proportional", + ref: "props.textSizeAbs", + defaultValue: true, + options: [ + { value: true, label: "Not proportional" }, + { value: false, label: "Proportional" } + ], + show: function (data) { + return data.props.showTexts != "N" + } + }, + textSizeFactor: { + type: "number", + label: "Text size proportion factor", + ref: "props.textSizeFactor", + defaultValue: 1, + show: function (data) { + return data.props.showTexts != "N" && !data.props.textSizeAbs; + } + }, + textSize: { + type: "number", + label: "Text size (px)", + ref: "props.textSize", + defaultValue: 10, + expression: "optional", + show: function (data) { + return data.props.showTexts != "N" && data.props.textSizeAbs; + } + }, + textSizeMax: { + type: "number", + label: "Max text size (px)", + ref: "props.textSize", + defaultValue: 18, + expression: "optional", + show: function (data) { + return data.props.showTexts != "N" && !data.props.textSizeAbs; + } + }, + textDots: { + type: "boolean", + component: "switch", + label: "Show text at all if ellipsis", + ref: "props.textDots", + defaultValue: true, + options: [ + { value: true, label: "Show" }, + { value: false, label: "Don't show" } + ], + show: function (data) { + return data.props.showTexts != "N"; + } + }, + textColor: { + type: "string", + component: "dropdown", + label: "Text color", + ref: "props.textColor", + defaultValue: "Auto", + options: [ + { value: "Auto", label: "Auto" }, + { value: "Black", label: "Black" }, + { value: "White", label: "White" } + ], + show: function (data) { + return data.props.showTexts != "N"; + } + }, + vAlign: { + type: "string", + component: "dropdown", + label: "Vertical alignment", + ref: "props.vAlign", + defaultValue: "C", + options: [ + { value: "C", label: "Center" }, + { value: "T", label: "Top" }, + { value: "B", label: "Bottom" } + ], + show: function (data) { + return data.props.showTexts != "N"; + } + }, + hAlign: { + type: "string", + component: "dropdown", + label: "Horizontal alignment", + ref: "props.hAlign", + defaultValue: "C", + options: [ + { value: "C", label: "Center" }, + { value: "L", label: "Left" }, + { value: "R", label: "Right" } + ], + show: function (data) { + return data.props.showTexts != "N"; + } + }, + totalFormatM: { + type: "string", + component: "dropdown", + label: "Total format", + ref: "props.totalFormatM", + defaultValue: "S", + options: [ + { value: "N", label: "Number" }, + { value: "P", label: "Percent" }, + { value: "S", label: "SI-notation (K, M, etc.)" }, + { value: "C", label: "Custom" } + ], + show: function (data) { + return data.props.showTexts != "N"; + } + }, + totalFormatMs: { + type: "string", + label: "Total format string", + ref: "props.totalFormatMs", + defaultValue: ",.3s", + expression: "optional", + show: function (data) { + return data.props.showTexts != "N" && data.props.totalFormatM == "C"; + } + }, + totalFormatMURL: { + type: "string", + component: "link", + label: "D3 format strings", + url: "https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md", + show: function (data) { + return data.props.showTexts != "N" && data.props.totalFormatM == "C"; + } + } + } + }, + transitions: { + type: "items", + label: "Transitions", + items: { + transitions: { + type: "boolean", + component: "switch", + label: "Transitions", + ref: "props.transitions", + defaultValue: true, + options: [ + { value: true, label: "Transitions enabled" }, + { value: false, label: "Transitions disabled" } + ] + }, + transitionDelay: { + type: "string", + label: "Transition delay", + ref: "props.transitionDelay", + defaultValue: "500", + expression: "optional", + show: function (data) { + return data.props.transitions; + } + }, + transitionDuration: { + type: "string", + label: "Transition duration", + ref: "props.transitionDuration", + defaultValue: "1000", + expression: "optional", + show: function (data) { + return data.props.transitions; + } + }, + transitionStyle: { + type: "string", + component: "dropdown", + label: "Transition style", + ref: "props.ease", + defaultValue: "linear", + options: [ + { value: "linear", label: "Linear" }, + { value: "quad", label: "Quad" }, + { value: "cubic", label: "Cubic" }, + { value: "sin", label: "Sine" }, + { value: "exp", label: "Exponential" }, + { value: "circle", label: "Circle" }, + { value: "elastic", label: "Elastic" }, + { value: "back", label: "Back" }, + { value: "bounce", label: "Bounce" } + ], + show: function (data) { + return data.props.transitions; + } + } + } + } + } + } + return { + type: "items", + component: "accordion", + items: { + dimensions: dimensions, + measures: measures, + sorting: sorting, + addons: addons, + appearance: appearance + } + }; + + }); diff --git a/src/barsPlus-template.html b/src/barsPlus-template.html new file mode 100644 index 0000000..8f74766 --- /dev/null +++ b/src/barsPlus-template.html @@ -0,0 +1,5 @@ + + + +
+
\ No newline at end of file diff --git a/barsPlus-test.html b/src/barsPlus-test.html similarity index 100% rename from barsPlus-test.html rename to src/barsPlus-test.html diff --git a/barsPlus.css b/src/barsPlus.css similarity index 92% rename from barsPlus.css rename to src/barsPlus.css index 5a02799..ac47b28 100644 --- a/barsPlus.css +++ b/src/barsPlus.css @@ -1,87 +1,95 @@ -/** - * barsPlus styles - * - * Author: L. Woodside - * Modification History: - * - * Version Person Date Description - * V1.0.0 L. Woodside 19-Dec-2016 Initial Release - * V1.1.0 L. Woodside 29-Dec-2016 Added text on bars - * -*/ -.qv-object-barsPlus - .ldwaxis path, - .ldwaxis line { - fill: none; - stroke: black; - shape-rendering: crispEdges; - } -.qv-object-barsPlus - .ldwaxis { - font-family: sans-serif; - font-size: 12px; - } -.qv-object-barsPlus - .ldwaxis .axisTitle { - font-family: sans-serif; - font-size: 12px; - } -.qv-object-barsPlus - .ldwaxis .tick line { - opacity: 0.2; - } -.qv-object-barsPlus - .ldwbar.selected { - stroke: black; - stroke-width: 2px; - } -.qv-object-barsPlus - .ldwtxt, .ldwtxtref, .ldwtot { - font-family: sans-serif; - pointer-events: none; - } -.qv-object-barsPlus - .ldwlegend text { - font-family: sans-serif; - font-size: 12px; - fill: #777; - } -.qv-object-barsPlus - .ldwtooltip { - position: fixed; - padding: 5px; - width: auto; - background-color: #404040; - border-radius: 5px; - z-index: 10; - pointer-events: none; - } -.qv-object-barsPlus - .ldwtooltip::after { - content: ""; - position: absolute; - bottom: -10px; - left: 50%; - border-width: 10px 10px 0; - border-style: solid; - border-color: #404040 transparent; - margin-left: -10px; - pointer-events: none; - } -.qv-object-barsPlus - .ldwtooltip .ldwttheading { - margin: 5px; - font-family: sans-serif; - font-size: 13px; - font-weight: bold; - line-height: 18px; - color: white; - } -.qv-object-barsPlus - .ldwtooltip .ldwttvalue { - margin: 5px; - font-family: sans-serif; - font-size: 13px; - line-height: 18px; - color: white; - } +/** + * barsPlus styles + * + * Author: L. Woodside + * Modification History: + * + * Version Person Date Description + * V1.0.0 L. Woodside 19-Dec-2016 Initial Release + * V1.1.0 L. Woodside 29-Dec-2016 Added text on bars + * +*/ +.qv-object-barsPlus + .ldwaxis path, + .ldwaxis line { + fill: none; + stroke: black; + shape-rendering: crispEdges; + } +.qv-object-barsPlus + .ldwaxis { + font-family: sans-serif; + font-size: 12px; + } +.qv-object-barsPlus + .ldwaxis .axisTitle { + font-family: sans-serif; + font-size: 12px; + } +.qv-object-barsPlus + .ldwaxis .tick line { + opacity: 0.2; + } +.qv-object-barsPlus + .ldwbar.selected { + stroke: black; + stroke-width: 2px; + } +.qv-object-barsPlus + .ldwtxt, .ldwtxtref, .ldwtot { + font-family: sans-serif; + pointer-events: none; + } +.qv-object-barsPlus + .ldwlegend text { + font-family: sans-serif; + font-size: 12px; + fill: #777; + } +.qv-object-barsPlus + .ldwtooltip { + position: fixed; + padding: 5px; + width: auto; + background-color: #404040; + border-radius: 5px; + z-index: 10; + pointer-events: none; + } +.qv-object-barsPlus + .ldwtooltip::after { + content: ""; + position: absolute; + bottom: -10px; + left: 50%; + border-width: 10px 10px 0; + border-style: solid; + border-color: #404040 transparent; + margin-left: -10px; + pointer-events: none; + } +.qv-object-barsPlus + .ldwtooltip .ldwttheading { + margin: 5px; + font-family: sans-serif; + font-size: 13px; + font-weight: bold; + line-height: 18px; + color: white; + } +.qv-object-barsPlus + .ldwtooltip .ldwttvalue { + margin: 5px; + font-family: sans-serif; + font-size: 13px; + line-height: 18px; + color: white; + } + +.qv-object-barsPlus .editMode { + position: absolute; + width: 100%; + height: 100%; + z-index: 1000; + top: 0; + } \ No newline at end of file diff --git a/barsPlus.js b/src/barsPlus.js similarity index 83% rename from barsPlus.js rename to src/barsPlus.js index f52f9ba..de98823 100644 --- a/barsPlus.js +++ b/src/barsPlus.js @@ -22,15 +22,24 @@ */ define( [ "jquery", + "qlik", "./barsPlus-initprops", "./barsPlus-props", "text!./barsPlus-template.html", "./barsPlus-directive" ], -function ($, initprops, props, template) { +function ($, qlik, initprops, props, template) { 'use strict'; + function isEditMode() { + if (qlik.navigation.getMode() === "analysis") { + return false; + } else { + return true; + } + } + return { initialProperties: initprops, definition: props, @@ -43,7 +52,8 @@ function ($, initprops, props, template) { // console.log('resize>',$element,layout,$element.scope()); // }, template: template, - controller: ['$scope', function($scope) { + controller: ['$scope', function($scope) { + $scope.isEditMode = isEditMode; }], paint: function ($element, layout) { var self = this; diff --git a/barsPlus.png b/src/barsPlus.png similarity index 100% rename from barsPlus.png rename to src/barsPlus.png diff --git a/barsPlus.qext b/src/barsPlus.qext similarity index 100% rename from barsPlus.qext rename to src/barsPlus.qext diff --git a/ldw-barsPlus.js b/src/ldw-barsPlus.js similarity index 96% rename from ldw-barsPlus.js rename to src/ldw-barsPlus.js index 970994e..21f597a 100644 --- a/ldw-barsPlus.js +++ b/src/ldw-barsPlus.js @@ -1,1464 +1,1464 @@ -/** - - barsPlus - Create D3 bar chart, stacked bar chart, area chart - - Author: L. Woodside - Modification History: - - Version Person Date Description - V1.0.0 L. Woodside 19-Dec-2016 Initial Release - V1.1.0 L. Woodside 29-Dec-2016 Added text on bars - V1.2.0 L. Woodside 07-Jan-2017 Allow multiple measures - V1.3.0 L. Woodside 15-Jan-2017 Improved color options - V1.3.1 L. Woodside 27-Jan-2017 Fix problem with legend properties - - Dependencies: d3.v3.js - - This script creates an object with the following methods: - - initData() Initialize chart data - initChart() Initialize a bar chart - createBars() Create the bars in the chart - updateBars() modify the bar chart due to changes in data - refreshChart() refresh chart (calls three above excluding initData) - - Presentation Properties - - orientation Orientation: H - Horizontal, V - Vertical - normalized Whether 100% bars - showDeltas Whether to show bar connectors (delta quadrangles) - barGap Spacing between bars, 0 - no space, 1 - no bars (area graph) - outerGap Spacing before first bar and after last bar - gridHeight Height of grid relative to highest bar - backgroundColor Grid background color - - Colors and Legend - - colorSource A - Assigned, C - Calculated - colorAttr Attribute is: O - an offset in scheme, C - a color value - colorScheme Named color scheme - colorOffset Offset of first color in color scheme (colorSource = Assigned) - singleColor Whether to use single color for 1-dimensional bars - showLegend Whether to show the legend - legendPosition Legend position: T - top, R - right, B - bottom, L - left - legendSize Legend size: N - narrow, M - medium, W - wide - legendSpacing Legend spacing: N - narrow, M - medium, W - wide - - Dimension Axis - - axisTitleD Dimension axis title - labelTitleD Dimension axis: B - labels & title, L - labels only, T - titles only, N - none - labelStyleD Dimension style: A - Auto, H - horizontal, S - staggered, T - tilted - gridlinesD Dimension gridlines - axisMarginD Dimension margin size: N - narrow, M - medium, W - wide - - Measure Axis - - axisTitleM Measure axis title - labelTitleM Measure axis: B - labels & title, L - labels only, T - titles only, N - none - labelStyleM Measure style: A - Auto, H - horizontal, S - staggered, T - tilted - gridlinesM Measure gridlines - axisMarginM Measure margin size: N - narrow, M - medium, W - wide - ticks Recommended number of ticks - axisFormatM Number format for measure axis, A - Auto, N - Number, P - Percent, S - SI, C - Custom - axisFormatMs Number format string for measure axis, D3 format - - Text on Bars - - showTexts Whether to show text on bars: B - on bars, T - total, A - both, N - none - showDim What to show in bars: M - measure, D - dimension, P - percent - showTot What to show for total: M - measure, D - dimension - innerBarPadH Horizontal inner bar padding (px) - innerBarPadV Vertical inner bar padding (px) - textSizeAbs Whether to use textSize instead of textSizeFactor (horizontal bars only) - textSizeFactor Text size as proportion of bar height (horizontal bars only) - textSize Text size (px), when vertical bars or textSizeAbs = false - textDots Whether to show text at all if ellipsis would be shown - textColor "Auto" - Choose white or black depending on bar color, else text color string - vAlign Vertical alignment: C - center, T - top, B - bottom - hAlign Horizontal alignment: C - Center, L - left, R - right - totalFormatM Number format for total: N - Number, P - Percent, S - SI, C - Custom - totalFormatMs Number format string for total, D3 format - - Transitions - - transitions Whether to enable transitions - transitionDelay Delay before start of transition - transitionDuration Duration of transition - ease Transition style - - Multiple measures - - defDims Defined number of dimensions - defMeas Defined number of measures - measures Array of measure names - - UI-determined Properties - - id Unique id of enclosing element - component D3 selection of enclosing element - width Width of enclosing element - height Height of enclosing element - inSelections Whether selection mode is enabled in Qlik Sense - editMode Whether edit mode is enabled in Qlik Sense - selectionMode Selection mode: QUICK or CONFIRM - rawData Raw data from hypercube - -*/ -var ldwbarsPlus = { - -/** - *-------------------------------------- - * Initialize Data - *-------------------------------------- - * This method will take input QV data and format it for 1 or 2 dimensions - * Input: g.rawData - * Output: g.data - * g.flatData - * g.allDim2 - * g.allCol2 - * g.nDims - * g.deltas -*/ -initData: function() { - /* - To support multiple measures, take the input raw data and transform it - to look like previously supported formats: - 0 Dimensions, 1 or more measures -> format as 1 dimension, 1 measure - 1 Dimension, 2 or more measures -> format as 2 dimensions, 1 measure - If two dimensions and multiple measures specified, ignore all but the first measure - */ - var g = this; - var struc = [], flatData = [], q = [], r = [], deltas = [], inData = []; - - if (!g.rawData[0]) return; // sometimes undefined - if (g.defDims + g.defMeas != g.rawData[0].length) return; // sometimes mismatched - - if (g.defDims == 0) { - for (var i = 0; i < g.rawData.length; i++) { - for (var j = 0; j < g.measures.length; j++) { - inData.push([ - {qElemNumber: -1, qNum: j+1, qText: g.measures[j]}, - g.rawData[i][j] - ]); - } - } - } - else if (g.defDims == 1 && g.defMeas > 1) { - for (var i = 0; i < g.rawData.length; i++) { - for (var j = 0; j < g.measures.length; j++) { - inData.push([ - g.rawData[i][0], - {qElemNumber: -1, qNum: j+1, qText: g.measures[j]}, - g.rawData[i][j+1] - ]); - } - } - } - else { - inData = g.rawData; // Process as in previous version - } - // If calculated color specified, must have provided Attribute - // otherwise set back to assigned mode - - var cx = g.defDims == 1 ? (g.defMeas == 1 ? 0 : 2) : 1; // index for attribute - if (g.colorSource == "C" && !inData[0][cx].qAttrExps) g.colorSource = "A"; - - // Function to get dimension/measure attribute for color - var cf = function(e) { - var cn = 0; - if (g.colorSource == "C") { - var cv = e[cx].qAttrExps.qValues[0].qNum; - if (!isNaN(cv)) - cn = Math.floor(Math.abs(cv)); - if (g.colorAttr == "C") { - // if text attribute exists, use it - if (e[cx].qAttrExps.qValues[0].qText) { - cn = e[cx].qAttrExps.qValues[0].qText; - } - else { - // number present, convert to hex - cv = cv.toString(16); - if (cv.length > 6) cv = cv.substring(cv.length-6); - cn = "#" + "000000".substring(0,6-cv.length) + cv; - } - } - } - return cn; - } - - // Process one dimension data - if (inData[0].length == 2) { - g.nDims = 1; - g.normalized = false; - inData.forEach(function(d) { - struc.push({dim1: d[0].qText, offset: d[1].qNum}); - flatData.push({ - dim1: d[0].qText, - dim2: d[0].qText, - offset: 0, - qNum: d[1].qNum, - qText: d[1].qText, - qTextPct: "", - qElemNumber: d[0].qElemNumber - }); - if(q.indexOf(d[0].qText) == -1) { - q.push(d[0].qText); - r.push(cf(d)); - } - }); - g.data = struc; - g.flatData = flatData; - g.allDim2 = q; - g.allCol2 = r; - return; - }; - - // Process two dimensional data - g.nDims = 2; - - var p1 = "", p2, edges = [], b, p = []; - inData.forEach(function(d) { - var c2 = d[1].qText; - if (p.indexOf(d[0].qText) == -1) { - p.push(d[0].qText); - } - if (q.indexOf(d[1].qText) == -1) { - q.push(d[1].qText); - r.push(cf(d)); - } - if (d[0].qText != p1) { - p1 = d[0].qText; - } - else { - b = false; - for (var i = 0; i < edges.length; i++) { - if (edges[i][0] == p2 && edges[i][1] == c2) { - b = true; - break; - } - } - if (!b) { - edges.push([p2,c2]) - } - } - p2 = c2; - }); - // Topological sort will throw an error if inconsistent data (sorting by measure) - // Just ignore errors and use original sort order - var qs, ps, rs = []; - try { - ps = q.slice(); - qs = this.toposort(q,edges); - // Replicate qs order in r - for (var i = 0; i < ps.length; i++) { - rs.push(r[ps.indexOf(qs[i])]); - } - r = rs; - } - catch(err) { - qs = q; - } - q = qs; - - var n = d3.nest() - .key(function(d) {return d[0].qText}) - .key(function(d) {return d[1].qText}) - .entries(inData) - ; - // sort all nodes in order specified by q - n.forEach(function(d) { - d.values.sort(function(a,b) { - return ( q.indexOf(a.key) < q.indexOf(b.key) ? -1 - : ( q.indexOf(a.key) > q.indexOf(b.key) ? 1 : 0)); - }); - }); - // nest messes up dim1 sort order, sort by order specified in p - n.sort(function(a,b) { - return ( p.indexOf(a.key) < p.indexOf(b.key) ? -1 - : ( p.indexOf(a.key) > p.indexOf(b.key) ? 1 : 0)); - }); - n.forEach(function(d, idx) { - var t = 0, v = [], j = 0, num, txt; - for (var i = 0; i < q.length; i++) { - if (d.values.length <= j || d.values[j].key != q[i]) { - num = 0; - txt = "-"; - elm = []; - } - else { - num = d.values[j].values[0][2].qNum; - txt = d.values[j].values[0][2].qText; - elm = d.values[j].values[0][0].qElemNumber; - j++; - } - v.push({ - dim2: q[i], - qNum: num, - qText: txt, - qElemNumber: elm, - offset: t - }); - t += num; - }; - v.forEach(function(e) { - e.dim1 = d.key; - if (g.normalized) { - e.offset = e.offset/t; - e.qNum = e.qNum/t; - e.qTextPct = d3.format(".1%")(e.qNum); - } - }); - flatData.push.apply(flatData,v); - struc.push({dim1: d.key, offset: t, values: v}); - - if (idx > 0 && g.showDeltas) { - var p = struc[idx-1].values; - var c = struc[idx].values; - for (var k = 0; k < p.length; k++) { - deltas.push({ - dim1p: p[k].dim1, - dim1c: c[k].dim1, - dim2: p[k].dim2, - delta: c[k].qNum - p[k].qNum, - deltaPct: 0, - points: [ - p[k].offset, - c[k].offset, - p[k].qNum, - c[k].qNum - ] - }); - } - } - - }); - g.data = struc; - g.flatData = flatData; - g.allDim2 = q; - g.allCol2 = r; - g.deltas = deltas; -}, - -/** - *-------------------------------------- - * Initialize Chart - *-------------------------------------- - * Set up initial elements, create axes, create legend - * create bars, deltas and legend items and bind data -*/ -initChart: function() { - - var g = this; - - var xLabelTitle = g.orientation == "V" ? g.labelTitleD : g.labelTitleM; - g.xAxisHeight = xLabelTitle == "B" || xLabelTitle == "L" - ? [70,40,25]["WMN".indexOf(g.orientation == "V" ? g.axisMarginD : g.axisMarginM)] : 0; - var xTitleHeight = xLabelTitle == "B" || xLabelTitle == "T" ? 20 : 0; - var xAxisPad = 20; - - var yLabelTitle = g.orientation == "V" ? g.labelTitleM : g.labelTitleD; - g.yAxisWidth = yLabelTitle == "B" || yLabelTitle == "L" - ? [90,50,30]["WMN".indexOf(g.orientation == "V" ? g.axisMarginM : g.axisMarginD)] : 0; - var yTitleWidth = yLabelTitle == "B" || yLabelTitle == "T" ? 20 : 0; - var yAxisPad = 20; - - var tr; // translate string - var dTitleHeight = g.labelTitleD == "B" || g.labelTitleD == "T" ? 20 : 0; - var margin = { - top: 10, //yAxisPad, - right: xAxisPad, - bottom: g.xAxisHeight + xTitleHeight + xAxisPad, - left: g.yAxisWidth + yTitleWidth + yAxisPad - }; - var innerWidth = g.width - margin.left - margin.right; - var innerHeight = g.height - margin.top - margin.bottom; - - g.lgn = { - minDim : [200,100], // min inner dimensions for legend to be displayed - use : "", - pad : 0, - sep : 5, - box : [12,12], // legend item color box - itmHeight : 20, - } - g.lgn.txtOff = g.lgn.box[0] + g.lgn.pad + g.lgn.sep; - - // adjust for legend if any - g.lgn.use = g.showLegend ? g.legendPosition : ""; - if (g.lgn.use) { - if (g.lgn.use == "L" || g.lgn.use == "R") { - if (innerWidth <= g.lgn.minDim[0]) { - g.lgn.use = ""; - } - else { - g.lgn.width = innerWidth/([4,6,10]["WMN".indexOf(g.legendSize)]); - innerWidth -= (g.lgn.width + yAxisPad); - g.lgn.height = innerHeight + g.xAxisHeight + xTitleHeight; - g.lgn.y = margin.top; - g.lgn.txtWidth = g.lgn.width - g.lgn.pad - g.lgn.sep - g.lgn.box[0]; - if (g.lgn.use == "L") { - g.lgn.x = yAxisPad; - margin.left += g.lgn.width + g.lgn.x; - } - else { - g.lgn.x = margin.left + innerWidth + yAxisPad; - } - } - } - else if (g.lgn.use == "T" || g.lgn.use == "B") { - if (innerHeight <= g.lgn.minDim[1]) { - g.lgn.use = ""; - } - else { - g.lgn.width = innerWidth + g.yAxisWidth + yTitleWidth; - g.lgn.height = g.lgn.itmHeight * (3 - "WMN".indexOf(g.legendSize)); - innerHeight -= g.lgn.height; - g.lgn.x = yAxisPad; - g.lgn.txtWidth = [100,75,50]["WMN".indexOf(g.legendSpacing)]; - if (g.lgn.use == "T") { - g.lgn.y = margin.top; - margin.top += g.lgn.height - } - else { - g.lgn.y = margin.bottom + innerHeight; - innerHeight -= 10; - } - } - } - } - g.component.selectAll("*") - .remove() - ; - var tooltip = g.component.append("div") - .attr("class","ldwtooltip") - .style("opacity","0") - ; - tooltip.append("p") - .attr("class","ldwttheading") - ; - tooltip.append("p") - .attr("class","ldwttvalue") - ; - g.svg = g.component - .append("svg") - .attr("width",g.width) - .attr("height",g.height) - .style("background-color",g.backgroundColor) - .append("g") - .attr("transform","translate(" + margin.left + "," + margin.top + ")") - ; - var dim1 = g.data.map(function(d) { return d.dim1; }); - if (g.orientation == "H") dim1.reverse() - g.dScale = d3.scale.ordinal() - .domain(dim1) - .rangeRoundBands(g.orientation == "V" ? [0, innerWidth] : [innerHeight,0], g.barGap, g.outerGap) - ; - g.mScale = d3.scale.linear() - .domain([0, d3.max(g.data, function(d) { return (g.normalized ? 1 : d.offset)*g.gridHeight; })]) - .range(g.orientation == "V" ? [innerHeight,0] : [0, innerWidth]) - .nice() - ; - var dGrp = g.svg.append("g") - .attr("class", "ldw-d ldwaxis"); - if (g.orientation == "V") { - dGrp.attr("transform", "translate(0," + innerHeight + ")"); - } - if (g.labelTitleD == 'B' || g.labelTitleD == 'L') { - g.dAxis = d3.svg.axis() - .scale(g.dScale) - .orient(g.orientation == "V" ? "bottom" : "left") - .tickSize(g.gridlinesD ? (g.orientation == "V" ? -innerHeight : -innerWidth) : 6) - .tickPadding(5) - ; - dGrp.call(g.dAxis); - } - if (g.labelTitleD == 'B' || g.labelTitleD == 'T') { - if(g.orientation == "V") { - tr = "translate(" + (innerWidth/2) + "," + (g.xAxisHeight + xTitleHeight) + ")"; - } - else { - tr = "translate(-" + (g.yAxisWidth + yTitleWidth/2 + 2) + "," + (innerHeight/2) + ")rotate(-90)"; - } - dGrp.append("text") - .attr("class","axisTitle") - .attr("text-anchor","middle") - .attr("transform",tr) - .text(g.axisTitleD) - ; - } - var mGrp = g.svg.append("g") - .attr("class", "ldw-m ldwaxis") - ; - if (g.orientation != "V") { - mGrp.attr("transform", "translate(0," + innerHeight + ")"); - } - if (g.labelTitleM == 'B' || g.labelTitleM == 'L') { - g.mAxis = d3.svg.axis() - .scale(g.mScale) - .orient(g.orientation == "V" ? "left" : "bottom") - .tickSize(g.gridlinesM ? (g.orientation == "V" ? -innerWidth : -innerHeight) : 6) - .ticks(g.ticks) - .tickFormat(d3.format([",.3s", ",.0f", ",.0%", ",.3s", g.axisFormatMs]["ANPSC".indexOf(g.axisFormatM)])) - .tickPadding(5) - ; - mGrp.call(g.mAxis); - } - if (g.labelTitleM == 'B' || g.labelTitleM == 'T') { - if(g.orientation == "V") { - tr = "translate(-" + (g.yAxisWidth + yTitleWidth/2 + 2) + "," + (innerHeight/2) + ")rotate(-90)"; - } - else { - tr = "translate(" + (innerWidth/2) + "," + (g.xAxisHeight + xTitleHeight) + ")"; - } - mGrp.append("text") - .attr("class","axisTitle") - .attr("text-anchor","middle") - .attr("transform",tr) - .text(g.axisTitleM) - ; - } - if (Object.getPrototypeOf(this).hasOwnProperty(g.colorScheme)) - ca = this[g.colorScheme]; - else - ca = d3.scale[g.colorScheme]().range(); - g.colorOffset %= ca.length; - // define various ways to get color - if (g.nDims == 1 && g.colorSource != "C" && g.singleColor) { - var rc = ca[g.colorOffset]; - g.cScale = function(d) { return rc; }; - } - else if (g.colorSource == "C") { - if (g.colorAttr == "O") { // color from calculated offset - g.cScale = function(d) { - return ca[g.allCol2[g.allDim2.indexOf(d)] % ca.length]; - } - } - else { // color is direct value - g.cScale = function(d) { - return g.allCol2[g.allDim2.indexOf(d)]; - } - } - } - else { - g.cScale = d3.scale.ordinal().range( - ca.slice(g.colorOffset,ca.length).concat(ca.slice(0,ca.length)) - ).domain(g.allDim2); - } - - // Create Legend - if (g.lgn.use) { - var lgn = d3.select("#" + g.id + " svg") - .append("g") - .attr("class","ldwlegend") - .attr("clip-path","url(#" + g.id + "_lgnClip)") - .attr("transform","translate(" + g.lgn.x + "," + g.lgn.y + ")") - ; - lgn.append("rect") - .attr("x",0) - .attr("y",0) - .attr("width",g.lgn.width) - .attr("height",g.lgn.height) - .style("fill",g.backgroundColor) - ; - lgn.append("clipPath") - .attr("id",g.id + "_lgnClip") - .append("rect") - .attr("x","0") - .attr("y","0") - .attr("width",g.lgn.width) - .attr("height",g.lgn.height) - ; - lgn.append("g") - .attr("class","ldwlgnitems") - } - // Create bars - g.bars = g.svg.selectAll("#" + g.id + " .ldwbar") - .data(g.flatData, function(d) { return d.dim1 + '|' + d.dim2; } ) - ; - // Text on bars - if (g.showTexts != "N") { - // Create text box for determining sizing - g.tref = g.svg.append("text") - .attr("x","0") - .attr("y","-100") - .attr("class","ldwtxtref") - ; - - if (~"TA".indexOf(g.showTexts) && !g.normalized) { - // Create bars totals - g.totals = g.svg.selectAll("#" + g.id + " .ldwtot") - .data(g.data, function(d) { return d.dim1; } ) - ; - } - if (~"BA".indexOf(g.showTexts)) { - // Create text on bars - g.texts = g.svg.selectAll("#" + g.id + " .ldwtxt") - .data(g.flatData, function(d) { return d.dim1 + '|' + d.dim2; } ) - ; - } - } - // Create deltas - if (g.showDeltas && g.nDims == 2) { - g.polys = g.svg.selectAll("#" + g.id + " polygon") - .data(g.deltas, function(d) { return d.dim1p + "-" + d.dim1c + "," + d.dim2; } ) - ; - } - // Create legend items - if (g.lgn.use) { - g.lgn.items = d3.select("#" + g.id + " .ldwlgnitems") - .selectAll("g") - .data(g.allDim2) - ; - } - -}, - -/** - *-------------------------------------- - * Create Bars - *-------------------------------------- - * Set up initial properties of bars, deltas, and legend - * Objects already have had data bound - * This procedure is also called from updateBars to add new items -*/ -createBars: function() { - - var g = this; - // Create bars - g.bars - .enter() - .append("rect") - .attr("ldwdim1", function(d) { return d.qElemNumber; }) - .attr(g.orientation == "V" ? "x" : "y", function(d) { return g.dScale(d.dim1); }) - .attr(g.orientation == "V" ? "y" : "x", function(d) { return g.mScale(0); }) // grow from bottom -// .attr(g.orientation == "V" ? "y" : "x", function(d) { return g.mScale(d.offset); }) // venetian blinds - .attr(g.orientation == "V" ? "width" : "height", g.dScale.rangeBand()) - .attr(g.orientation == "V" ? "height" : "width", function(d) { return 0; }) - .style("fill", function(d) { return g.cScale(d.dim2); }) - .style("opacity","0") - .attr("class","selectable ldwbar") - .on("click",function(d) { - if (d.qElemNumber >= 0) { // Cannot select a measure - if (g.selectionMode == "QUICK") { - g.self.backendApi.selectValues(0,[d.qElemNumber],true); - } - else if (g.selectionMode == "CONFIRM") { - var t = d3.select(this).classed("selected"); - g.self.selectValues(0,[d.qElemNumber],true); - // following to address QS bug where clear button does not clear class names - g.self.clearSelectedValues = function() { - d3.selectAll("#" + g.id +" .selected").classed("selected",false); - }; - var x = d3.selectAll("#" + g.id + " [ldwdim1='" + d.qElemNumber + "']") - .classed("selected",!t); - d3.select("#" + g.id + " .ldwtooltip") - .style("opacity","0") - .transition() - .remove - ; - } - } - return; - }) - .on("mouseenter",function(d) { - if (g.inSelections || g.editMode) return; - d3.select(this) - .style("opacity","0.5") - .attr("stroke","white") - .attr("stroke-width","2") - ; - // Place text in tooltip - d3.select("#" + g.id + " .ldwttheading") - .text(g.nDims == 2 ? d.dim1 + ", " + d.dim2 : d.dim1); - d3.select("#" + g.id + " .ldwttvalue") - .text(g.nDims == 2 ? - (g.normalized ? d.qTextPct + ", " + d.qText : d.qText) - : d.qText); - - var matrix = this.getScreenCTM() - .translate(+this.getAttribute("x"),+this.getAttribute("y")); - - var xPosition = (window.pageXOffset + matrix.e) - - d3.select("#" + g.id + " .ldwtooltip")[0][0].clientWidth/2 - + (g.orientation == "V" ? g.dScale.rangeBand() : d3.select(this).attr("width"))/2 - ; - var yPosition = (window.pageYOffset + matrix.f) - - d3.select("#" + g.id + " .ldwtooltip")[0][0].clientHeight - -10 - ; - d3.select("#" + g.id + " .ldwtooltip") - .style("left", xPosition + "px") - .style("top", yPosition + "px") - .transition() - .delay(750) - .style("opacity","0.95") - ; - }) - .on("mouseleave",function() { - d3.select(this) - .style("opacity","1.0") - .attr("stroke","none") - ; - d3.select("#" + g.id + " .ldwtooltip") - .style("opacity","0") - .transition() - .remove - ; - }) - ; - - if (~"TA".indexOf(g.showTexts) && !g.normalized) { - // Create totals - g.totals - .enter() - .append("text") - .attr("class","ldwtot") - .style("opacity","0") - .each(function(d) { - d.qNum = g.mScale.domain()[1] - d.offset; - d.qText = d3.format([",.0f", ",.0%", ",.3s", g.totalFormatMs]["NPSC".indexOf(g.totalFormatM)])(d.offset); - var txp = g.barText(d, true); - - d3.select(this) - .style("fill", "black") - .style("font-size", g.tref.style("font-size")) - .attr("x",g.orientation == "V" ? txp.x : 0) - .attr("y",g.orientation == "V" ? g.mScale(0) : txp.y) - .attr("dy","-.2em") - .text(txp.text) - ; - - }) - ; - } - - if (~"BA".indexOf(g.showTexts)) { - // Create text inside bars - g.texts - .enter() - .append("text") - .attr("class","ldwtxt") - .style("opacity","0") - .each(function(d) { - var txp = g.barText(d); - - d3.select(this) - .style("fill", g.textColor == "Auto" ? g.txtColor(g.cScale(d.dim2)) : g.textColor ) - .style("font-size", g.tref.style("font-size")) - .attr("x",g.orientation == "V" ? txp.x : 0) - .attr("y",g.orientation == "V" ? g.mScale(0) : txp.y) - .attr("dy","-.2em") - .text(txp.text) - ; - - }) - ; - } - - if (g.showDeltas && g.nDims == 2) { - - // Create deltas - g.polys - .enter() - .append("polygon") - .attr("points",function(d) { - var p; - if (g.orientation == "V") { - p = (g.dScale(d.dim1p)+g.dScale.rangeBand()) + "," + - g.mScale(0) + " " + - (g.dScale(d.dim1p)+g.dScale.rangeBand()) + "," + - g.mScale(0) + " " + - (g.dScale(d.dim1c)) + "," + - g.mScale(0) + " " + - (g.dScale(d.dim1c)) + "," + - g.mScale(0) - ; - } - else { - p = g.mScale(0) + "," + (g.dScale(d.dim1p) + g.dScale.rangeBand()) + " " + - g.mScale(0) + "," + (g.dScale(d.dim1p) + g.dScale.rangeBand()) + " " + - g.mScale(0) + "," + g.dScale(d.dim1c) + " " + - g.mScale(0) + "," + g.dScale(d.dim1c) - ; - } - return p; - }) - .style("fill",function(d) { - return g.cScale(d.dim2); - }) - .style("opacity","0") - .on("mouseenter",function(d) { - var pt = this.getAttribute("points").split(" "); - var sx = 0, sy = 0; - pt.forEach(function(e, i) { - var x = e.split(","); - if (g.orientation == "H") { - if (i < 2) { - sx += parseFloat(x[0]); - sy += parseFloat(x[1]); - } - } - else if (i == 0 || i == 3) { - sx += parseFloat(x[0]); - sy += parseFloat(x[1]); - } - } ); - sx /= 2; - sy /= 2; - - if (g.inSelections || g.editMode) return; - - d3.select(this) - .style("opacity","0.5") - .attr("stroke","white") - .attr("stroke-width","2"); - // Place text in tooltip - d3.select("#" + g.id + " .ldwttheading") - .text(d.dim2 + ", " + d.dim1p + "-" + d.dim1c); - d3.select("#" + g.id + " .ldwttvalue") - .text(d3.format(g.normalized ? "+.1%" : "+.3s")(d.delta)); - - var matrix = this.getScreenCTM() - .translate(sx,sy); - - var xPosition = (window.pageXOffset + matrix.e) - - d3.select("#" + g.id + " .ldwtooltip")[0][0].clientWidth/2 - ; - var yPosition = (window.pageYOffset + matrix.f) - - d3.select("#" + g.id + " .ldwtooltip")[0][0].clientHeight - -10 - ; - d3.select("#" + g.id + " .ldwtooltip") - .style("left", xPosition + "px") - .style("top", yPosition + "px") - .transition() - .delay(750) - .style("opacity","0.95") - ; - }) - .on("mouseleave",function() { - d3.select(this) - .style("opacity",g.barGap == 1 ? "1" : "0.5") - .attr("stroke","none") - ; - d3.select("#" + g.id + " .ldwtooltip") - .style("opacity","0") - .transition() - .remove; - }) - ; - } - // create legend - if (g.lgn.use) { - g.lgn.items - .enter() - .append("g") - .attr("class","ldwlgnitem") - .each(function(d, i) { - d3.select(this) - .append("rect") -// .attr("x","0") // Initialize to zero to have legend grow from top -// .attr("y","0") - .attr("x",function(e) { - var x; - if (g.lgn.use == "T" || g.lgn.use == "B") { - x = i*(g.lgn.txtOff + g.lgn.txtWidth); - } - else { - x = g.lgn.pad; - } - return x; - }) - .attr("y",function(e) { - var y; - if (g.lgn.use == "T" || g.lgn.use == "B") { - y = g.lgn.pad; - } - else { - y = g.lgn.pad + g.lgn.itmHeight*i; - } - return y; - }) - .style("opacity","0") - .attr("width",g.lgn.box[0]) - .attr("height",g.lgn.box[1]) - .style("fill",function(e) { return g.cScale(e); }) - ; - d3.select(this) - .append("text") -// .attr("x","0") // Initialize to zero to have legend grow from top -// .attr("y","0") - .attr("x",function(e) { - var x; - if (g.lgn.use == "T" || g.lgn.use == "B") { - x = i*(g.lgn.txtOff + g.lgn.txtWidth) + g.lgn.txtOff; - } - else { - x = g.lgn.txtOff; - } - return x; - }) - .attr("y",function(e) { - var y; - if (g.lgn.use == "T" || g.lgn.use == "B") { - y = g.lgn.pad + 11; - } - else { - y = g.lgn.pad + g.lgn.itmHeight*i + 11; - } - return y; - }) - .style("opacity","0") - .text(function(e) { - return e; - }) - ; - }) - ; - } -}, -/** - *-------------------------------------- - * Bar Text - *-------------------------------------- - * Get bar text information: x, y and text -*/ -barText: function(d, total) { - var tx, txt, origX, bHeight, textX, bb, textY, textLength, ts; - var g = this; - - // Relative text sizing, relative to bar width - // For total, make larger by reducing unneeded padding - var hAlign = g.hAlign, vAlign = g.vAlign, - innerBarPadV = +g.innerBarPadV, - innerBarPadH = +g.innerBarPadH; - if (g.textSizeAbs) - ts = g.textSize; - else { - if (total) - if (g.orientation == "H") - innerBarPadV = 1; - else - innerBarPadH = 1; - ts = g.textSizeFactor * (g.dScale.rangeBand() - 2 * (g.orientation == "H" ? innerBarPadV : innerBarPadH)) - // limit size to maximum, also stored in textSize - if (ts > g.textSize) ts = g.textSize; - } - g.tref.style("font-size", ts); - - if (total) { // Override some alignment for total - if (g.orientation == "V") - vAlign = "B" - else - hAlign = "L" - } - origX = g.orientation == "V" ? g.dScale(d.dim1) : g.mScale(d.offset); - bHeight = g.orientation == "V" ? g.mScale(0) - g.mScale(d.qNum) : g.dScale.rangeBand(); - textX = g.orientation == "V" ? g.dScale.rangeBand() : g.mScale(d.qNum); - - g.tref.text(d.qNum == 0 ? "" - : (total - ? (g.showTot == "D" ? d.dim1 : d.qText) - : (g.showDim == "D" ? d.dim2 : (g.showDim == "P" && g.normalized ? d.qTextPct : d.qText)) - ) - ); - - bb = g.tref.node().getBBox(); - tx = origX + innerBarPadH; - if (vAlign == "C") - textY = g.orientation == "V" ? g.mScale(d.offset) - (g.mScale(0) - g.mScale(d.qNum)) + - (g.mScale(0) - g.mScale(d.qNum) + bb.height)/2 - : g.dScale(d.dim1) + (g.dScale.rangeBand() + bb.height)/2; - else if (vAlign == "T") - textY = g.orientation == "V" ? g.mScale(d.offset) - (g.mScale(0) - g.mScale(d.qNum)) + - bb.height + innerBarPadV - : g.dScale(d.dim1) + innerBarPadV + bb.height; - else - textY = g.orientation == "V" ? g.mScale(d.offset) - innerBarPadV - : g.dScale(d.dim1) + g.dScale.rangeBand() - innerBarPadV; - txt = ""; - if (bb.height + 2 * innerBarPadV <= bHeight || (g.orientation != "V" && !g.textSizeAbs)) { - if (bb.width + 2 * innerBarPadH <= textX) { - if (hAlign == "C") { - tx = origX + (textX - bb.width)/2; - } - else if (hAlign == "R") { - tx = origX + textX - bb.width - innerBarPadH; - } - txt = g.tref.text(); - } - else if (g.textDots) { - textLength = g.tref.node().getComputedTextLength(); - txt = g.tref.text(); - while (textLength > textX - 2 * innerBarPadH && txt.length > 0) { - txt = txt.slice(0, -1); - g.tref.text(txt + '\u2026'); - textLength = g.tref.node().getComputedTextLength(); - } - if (txt.length != 0) txt = g.tref.text(); - } - } - return {x: tx, y: textY, text: txt}; - -}, -/** - *-------------------------------------- - * Update Bars - *-------------------------------------- - * Modify properties of bars, deltas, and legend -*/ -updateBars: function() { - - var g = this; - - var dim1 = g.data.map(function(d) { return d.dim1; }); - if (g.orientation == "H") dim1.reverse() - g.dScale.domain(dim1); - g.mScale.domain( [0, d3.max(g.data, function(d) { return (g.normalized ? 1 : d.offset)*g.gridHeight; })]); - var tDelay = g.transitions && !g.editMode ? g.transitionDelay : 0; - var tDuration = g.transitions && !g.editMode ? g.transitionDuration : 0; - - // Procedure to update dimension and measure axes - var updateAxis = function (labelTitle, labelStyle, axis, axisClass, isXAxis, axisWidth) { - - if (labelTitle == 'B' || labelTitle == 'L') { - // Update axis with transition - g.svg.select("#" + g.id + " ." + axisClass + ".ldwaxis") - .transition() - .delay(tDelay) - .duration(tDuration) - .ease(g.ease) - .call(axis) - ; - var lbl = d3.selectAll("#" + g.id + " ." + axisClass + ".ldwaxis"); - var txt = lbl.selectAll(".tick.major text") - .attr("transform",null); // All horizontal initially - var maxWidth = isXAxis ? lbl.node().getBBox().width/txt[0].length : g.yAxisWidth - 5; - - // If auto labels and any overlap, set to tilted - if (labelStyle == "A") { - txt.each(function(d, i) { - if (d3.select(this).node().getComputedTextLength() > maxWidth) { - labelStyle = "T" // no break for each - } - }) - ; - } - // Tilted labels - if (labelStyle == "T") { - txt.style("text-anchor","end") - .attr("transform","translate(" + (isXAxis ? "-12,0" : "-2,-8") + ") rotate(-45)") - ; - maxWidth = isXAxis ? g.xAxisHeight - 5 : maxWidth * Math.sqrt(2); - } - // Staggered labels - else if (labelStyle == "S" && isXAxis) { - txt.each(function(d, i) { - if (i % 2 == 1) { - d3.select(this).attr("transform","translate(0,14)"); - } - }) - ; - } - // Horizontal or titled labels, use ellipsis if overlap - if (labelStyle == "H" || labelStyle == "T") { - txt.each(function(d, i) { - var self = d3.select(this), - textLength = self.node().getComputedTextLength(), - text = self.text(); - while (textLength > maxWidth && text.length > 0) { - text = text.slice(0, -1); - self.text(text + '\u2026'); - textLength = self.node().getComputedTextLength(); - } - }); - } - } - }; - // Update dimension axis - updateAxis(g.labelTitleD, g.labelStyleD, g.dAxis, "ldw-d", g.orientation == "V"); - // Update measure axis - updateAxis(g.labelTitleM, g.labelStyleM, g.mAxis, "ldw-m", g.orientation != "V"); - - g.bars = g.svg.selectAll("#" + g.id + " .ldwbar") - .data(g.flatData, function(d) { return d.dim1 + '|' + d.dim2; } ) - ; - // Remove bars with transition - g.bars - .exit() - .transition() - .delay(tDelay) - .duration(tDuration) - .ease(g.ease) - .style("opacity", "0") - .remove() - ; - - // Remove totals with transition - if (~"TA".indexOf(g.showTexts)) { - g.totals = g.svg.selectAll("#" + g.id + " .ldwtot") - .data(g.data, function(d) { return d.dim1; } ) - ; - g.totals - .exit() - .transition() - .delay(tDelay) - .duration(tDuration) - .ease(g.ease) - .style("opacity", "0") - .remove() - ; - } - // Remove texts with transition - if (~"BA".indexOf(g.showTexts)) { - g.texts = g.svg.selectAll("#" + g.id + " .ldwtxt") - .data(g.flatData, function(d) { return d.dim1 + '|' + d.dim2; } ) - ; - g.texts - .exit() - .transition() - .delay(tDelay) - .duration(tDuration) - .ease(g.ease) - .style("opacity", "0") - .remove() - ; - } - - if (g.showDeltas && g.nDims == 2) { - g.polys = g.svg.selectAll("#" + g.id + " polygon") - .data(g.deltas, function(d) { return d.dim1p + "-" + d.dim1c + "," + d.dim2; } ) - ; - // Remove deltas with transition - g.polys - .exit() - .transition() - .delay(tDelay) - .duration(tDuration) - .ease(g.ease) - .style("opacity", "0") - .remove() - ; - } - // remove legend items with transition - if (g.lgn.use) { - g.lgn.items = d3.selectAll("#" + g.id + " .ldwlgnitems") - .selectAll("g") - .data(g.allDim2, function(d) { return d; }) - ; - g.lgn.items - .exit() - .transition() - .delay(tDelay) - .duration(tDuration) - .ease(g.ease) - .style("opacity", "0") - .remove() - ; - } - - // Add any new bars/deltas/legend items - this.createBars(); - - // Update bars - if (g.orientation == "V") { - g.bars.transition() - .delay(tDelay) - .duration(tDuration) - .ease(g.ease) - .style("opacity","1") - .attr("x", function(d, i) { - return g.dScale(d.dim1) ? g.dScale(d.dim1) : 0; // ignore NaN: causing errors in transitions - }) - .attr("y", function(d) { - return g.mScale(d.offset) - (g.mScale(0) - g.mScale(d.qNum)); - }) - .attr("width", g.dScale.rangeBand() && g.dScale.rangeBand() > 0 ? g.dScale.rangeBand() : 0) // ignore NaN: causing errors in transitions - .attr("height", function(d) { - return g.mScale(0) > g.mScale(d.qNum) ? g.mScale(0) - g.mScale(d.qNum) : 0; // ignore negatives: causing errors in transitions - }) - ; - } - else { - g.bars.transition() - .delay(tDelay) - .duration(tDuration) - .ease(g.ease) - .style("opacity","1") - .attr("x", function(d) { - return g.mScale(d.offset); - }) - .attr("y", function(d, i) { - return g.dScale(d.dim1); - }) - .attr("width", function(d) { - return g.mScale(d.qNum); - }) - .attr("height", g.dScale.rangeBand()) - ; - } - - if (~"TA".indexOf(g.showTexts) && !g.normalized) { - // Update totals - g.totals - .each(function(d) { - d.qNum = g.mScale.domain()[1] - d.offset; - d.qText = d3.format([",.0f", ",.0%", ",.3s", g.totalFormatMs]["NPSC".indexOf(g.totalFormatM)])(d.offset); - var txp = g.barText(d, true); - d3.select(this) - .transition() - .delay(tDelay) - .duration(tDuration) - .ease(g.ease) - .style("opacity","1") - .style("fill", "black") - .style("font-size", g.tref.style("font-size")) - .attr({x: txp.x, y: txp.y, dy: "-.2em"}) - .text(txp.text) - ; - - }) - ; - } - - if (~"BA".indexOf(g.showTexts)) { - // Update texts - g.texts - .each(function(d) { - var txp = g.barText(d); - - d3.select(this) - .transition() - .delay(tDelay) - .duration(tDuration) - .ease(g.ease) - .style("opacity","1") - .style("fill", g.textColor == "Auto" ? g.txtColor(g.cScale(d.dim2)) : g.textColor ) - .style("font-size", g.tref.style("font-size")) - .attr({x: txp.x, y: txp.y, dy: "-.2em"}) - .text(txp.text) - ; - - }) - ; - } - - if (g.showDeltas && g.nDims == 2) { - - // update deltas - g.polys.transition() - .delay(tDelay) - .duration(tDuration) - .ease(g.ease) - .attr("points",function(d) { - var p; - if (g.orientation == "V") { - p = (g.dScale(d.dim1p)+g.dScale.rangeBand()) + "," + - (g.mScale(d.points[0]) - (g.mScale(0) - g.mScale(d.points[2]))) + " " + - - (g.dScale(d.dim1p)+g.dScale.rangeBand()) + "," + - g.mScale(d.points[0]) + " " + - - (g.dScale(d.dim1c)) + "," + - g.mScale(d.points[1]) + " " + - - (g.dScale(d.dim1c)) + "," + - (g.mScale(d.points[1]) - (g.mScale(0) - g.mScale(d.points[3]))) - ; - } - else { - p = (g.mScale(d.points[0]) + g.mScale(d.points[2])) + "," + - (g.dScale(d.dim1p) + g.dScale.rangeBand()) + " " + - - g.mScale(d.points[0]) + "," + - (g.dScale(d.dim1p) + g.dScale.rangeBand()) + " " + - - g.mScale(d.points[1]) + "," + - g.dScale(d.dim1c) + " " + - - (g.mScale(d.points[1]) + g.mScale(d.points[3])) + "," + - g.dScale(d.dim1c) - ; - } - return p; - }) - .style("fill",function(d) { - return g.cScale(d.dim2); - }) - .style("opacity",g.barGap == 1 ? "1" : "0.5") - ; - } - // update legend items - if (g.lgn.use) { - - if (g.lgn.use == "T" || g.lgn.use == "B") { - var maxprow = Math.floor(g.lgn.width/(g.lgn.txtOff + g.lgn.txtWidth)); - var nprow = maxprow; - } - - g.lgn.items - .each(function(d, i) { - d3.select(this) - .transition() - .delay(tDelay) - .duration(tDuration) - .select("rect") - .attr("x",function(e) { - var x; - if (g.lgn.use == "T" || g.lgn.use == "B") { - x = (i % nprow)*(g.lgn.txtOff + g.lgn.txtWidth); - } - else { - x = g.lgn.pad; - } - return x; - }) - .attr("y",function(e) { - var y; - if (g.lgn.use == "T" || g.lgn.use == "B") { - y = g.lgn.pad + Math.floor(i/nprow) * g.lgn.itmHeight; - } - else { - y = g.lgn.pad + g.lgn.itmHeight*i; - } - return y; - }) - .style("opacity","1") - ; - var txt = d3.select(this) - .transition() - .delay(tDelay) - .duration(tDuration) - .select("text") - .attr("x",function(e) { - var x; - if (g.lgn.use == "T" || g.lgn.use == "B") { - x = (i % nprow)*(g.lgn.txtOff + g.lgn.txtWidth) + g.lgn.txtOff; - } - else { - x = g.lgn.txtOff; - } - return x; - }) - .attr("y",function(e) { - var y; - if (g.lgn.use == "T" || g.lgn.use == "B") { - y = g.lgn.pad + Math.floor(i/nprow) * g.lgn.itmHeight + 11; - } - else { - y = g.lgn.pad + g.lgn.itmHeight*i + 11; - } - return y; - }) - .style("opacity","1") - ; - txt.each(function(d, i) { - var self = d3.select(this), - textLength = self.node().getComputedTextLength(), - text = self.text(); - while (textLength > g.lgn.txtWidth && text.length > 0) { - text = text.slice(0, -1); - self.text(text + '\u2026'); - textLength = self.node().getComputedTextLength(); - } - }); - ; - }) - ; - } -}, - -/** - *-------------------------------------- - * Refresh chart - *-------------------------------------- - * Refresh chart, no new data -*/ -refreshChart: function() { - - this.initChart(); - this.createBars(); - this.updateBars(); -}, -//-------------------------------------- -// Topological sort -//-------------------------------------- -/*- begin https://github.com/marcelklehr/toposort */ -toposort: function(nodes, edges) { - var cursor = nodes.length - , sorted = new Array(cursor) - , visited = {} - , i = cursor - - while (i--) { - if (!visited[i]) visit(nodes[i], i, []) - } - - return sorted - - function visit(node, i, predecessors) { - if(predecessors.indexOf(node) >= 0) { - throw new Error('Cyclic dependency: '+JSON.stringify(node)) - } - -// if (!~nodes.indexOf(node)) { -// throw new Error('Found unknown node. Make sure to provided all involved nodes. Unknown node: '+JSON.stringify(node)) -// } - - if (visited[i]) return; - visited[i] = true - - // outgoing edges - var outgoing = edges.filter(function(edge){ - return edge[0] === node - }) - if (i = outgoing.length) { - var preds = predecessors.concat(node) - do { - var child = outgoing[--i][1] - visit(child, nodes.indexOf(child), preds) - } while (i) - } - - sorted[--cursor] = node - } -}, -/*- end https://github.com/marcelklehr/toposort */ -/*- begin http://stackoverflow.com/questions/11867545 */ -txtColor: function(hexcolor){ - var r = parseInt(hexcolor.substr(1,2),16); - var g = parseInt(hexcolor.substr(3,2),16); - var b = parseInt(hexcolor.substr(5,2),16); - var yiq = ((r*299)+(g*587)+(b*114))/1000; - return (yiq >= 160) ? 'black' : 'white'; // 128 changed to 160 to give white preference -}, -/*- end http://stackoverflow.com/questions/11867545 */ -//-------------------------------------- -// Color Schemes -//-------------------------------------- -custom100: [ -"#99c867","#e43cd0","#e2402a","#66a8db","#3f1a20","#e5aa87","#3c6b59","#aa2a6b","#e9b02e","#7864dd", -"#65e93c","#5ce4ba","#d0e0da","#d796dd","#64487b","#e4e72b","#6f7330","#932834","#ae6c7d","#986717", -"#e3cb70","#408c1d","#dd325f","#533d1c","#2a3c54","#db7127","#72e3e2","#e2c1da","#d47555","#7d7f81", -"#54ae9b","#e9daa6","#3a8855","#5be66e","#ab39a4","#a6e332","#6c469d","#e39e51","#4f1c42","#273c1c", -"#aa972e","#8bb32a","#bdeca5","#63ec9b","#9c3519","#aaa484","#72256d","#4d749f","#9884df","#e590b8", -"#44b62b","#ad5792","#c65dea","#e670ca","#e38783","#29312d","#6a2c1e","#d7b1aa","#b1e7c3","#cdc134", -"#9ee764","#56b8ce","#2c6323","#65464a","#b1cfea","#3c7481","#3a4e96","#6493e1","#db5656","#747259", -"#bbabe4","#e33f92","#d0607d","#759f79","#9d6b5e","#8574ae","#7e304c","#ad8fac","#4b77de","#647e17", -"#b9c379","#8da8b0","#b972d9","#786279","#7ec07d","#916436","#2d274f","#dce680","#759748","#dae65a", -"#459c49","#b7934a","#51c671","#9ead3f","#969a5c","#b9976a","#46531a","#c0f084","#76c146","#bad0ad" -], -google20: [ -"#3366cc","#dc3912","#ff9900","#109618","#990099","#0099c6","#dd4477","#66aa00","#b82e2e","#316395", -"#994499","#22aa99","#aaaa11","#6633cc","#e67300","#8b0707","#651067","#329262","#5574a6","#3b3eac" -], -qlikView18: [ -"#8daacb","#fc7362","#bbd854","#ffd92f","#66c296","#e5b694","#e78ad2","#b3b3b3","#a6d8e3","#abe9bc", -"#1b7d9c","#ffbfc9","#4da741","#c4b2d6","#b22424","#00acac","#be6c2c","#695496" -], -qlikSense12: [ -"#332288","#6699cc","#88ccee","#44aa99","#117733","#999933","#ddcc77","#661100","#cc6677","#aa4466", -"#882255","#aa4499" -] +/** + + barsPlus - Create D3 bar chart, stacked bar chart, area chart + + Author: L. Woodside + Modification History: + + Version Person Date Description + V1.0.0 L. Woodside 19-Dec-2016 Initial Release + V1.1.0 L. Woodside 29-Dec-2016 Added text on bars + V1.2.0 L. Woodside 07-Jan-2017 Allow multiple measures + V1.3.0 L. Woodside 15-Jan-2017 Improved color options + V1.3.1 L. Woodside 27-Jan-2017 Fix problem with legend properties + + Dependencies: d3.v3.js + + This script creates an object with the following methods: + + initData() Initialize chart data + initChart() Initialize a bar chart + createBars() Create the bars in the chart + updateBars() modify the bar chart due to changes in data + refreshChart() refresh chart (calls three above excluding initData) + + Presentation Properties + + orientation Orientation: H - Horizontal, V - Vertical + normalized Whether 100% bars + showDeltas Whether to show bar connectors (delta quadrangles) + barGap Spacing between bars, 0 - no space, 1 - no bars (area graph) + outerGap Spacing before first bar and after last bar + gridHeight Height of grid relative to highest bar + backgroundColor Grid background color + + Colors and Legend + + colorSource A - Assigned, C - Calculated + colorAttr Attribute is: O - an offset in scheme, C - a color value + colorScheme Named color scheme + colorOffset Offset of first color in color scheme (colorSource = Assigned) + singleColor Whether to use single color for 1-dimensional bars + showLegend Whether to show the legend + legendPosition Legend position: T - top, R - right, B - bottom, L - left + legendSize Legend size: N - narrow, M - medium, W - wide + legendSpacing Legend spacing: N - narrow, M - medium, W - wide + + Dimension Axis + + axisTitleD Dimension axis title + labelTitleD Dimension axis: B - labels & title, L - labels only, T - titles only, N - none + labelStyleD Dimension style: A - Auto, H - horizontal, S - staggered, T - tilted + gridlinesD Dimension gridlines + axisMarginD Dimension margin size: N - narrow, M - medium, W - wide + + Measure Axis + + axisTitleM Measure axis title + labelTitleM Measure axis: B - labels & title, L - labels only, T - titles only, N - none + labelStyleM Measure style: A - Auto, H - horizontal, S - staggered, T - tilted + gridlinesM Measure gridlines + axisMarginM Measure margin size: N - narrow, M - medium, W - wide + ticks Recommended number of ticks + axisFormatM Number format for measure axis, A - Auto, N - Number, P - Percent, S - SI, C - Custom + axisFormatMs Number format string for measure axis, D3 format + + Text on Bars + + showTexts Whether to show text on bars: B - on bars, T - total, A - both, N - none + showDim What to show in bars: M - measure, D - dimension, P - percent + showTot What to show for total: M - measure, D - dimension + innerBarPadH Horizontal inner bar padding (px) + innerBarPadV Vertical inner bar padding (px) + textSizeAbs Whether to use textSize instead of textSizeFactor (horizontal bars only) + textSizeFactor Text size as proportion of bar height (horizontal bars only) + textSize Text size (px), when vertical bars or textSizeAbs = false + textDots Whether to show text at all if ellipsis would be shown + textColor "Auto" - Choose white or black depending on bar color, else text color string + vAlign Vertical alignment: C - center, T - top, B - bottom + hAlign Horizontal alignment: C - Center, L - left, R - right + totalFormatM Number format for total: N - Number, P - Percent, S - SI, C - Custom + totalFormatMs Number format string for total, D3 format + + Transitions + + transitions Whether to enable transitions + transitionDelay Delay before start of transition + transitionDuration Duration of transition + ease Transition style + + Multiple measures + + defDims Defined number of dimensions + defMeas Defined number of measures + measures Array of measure names + + UI-determined Properties + + id Unique id of enclosing element + component D3 selection of enclosing element + width Width of enclosing element + height Height of enclosing element + inSelections Whether selection mode is enabled in Qlik Sense + editMode Whether edit mode is enabled in Qlik Sense + selectionMode Selection mode: QUICK or CONFIRM + rawData Raw data from hypercube + +*/ +var ldwbarsPlus = { + +/** + *-------------------------------------- + * Initialize Data + *-------------------------------------- + * This method will take input QV data and format it for 1 or 2 dimensions + * Input: g.rawData + * Output: g.data + * g.flatData + * g.allDim2 + * g.allCol2 + * g.nDims + * g.deltas +*/ +initData: function() { + /* + To support multiple measures, take the input raw data and transform it + to look like previously supported formats: + 0 Dimensions, 1 or more measures -> format as 1 dimension, 1 measure + 1 Dimension, 2 or more measures -> format as 2 dimensions, 1 measure + If two dimensions and multiple measures specified, ignore all but the first measure + */ + var g = this; + var struc = [], flatData = [], q = [], r = [], deltas = [], inData = []; + + if (!g.rawData[0]) return; // sometimes undefined + if (g.defDims + g.defMeas != g.rawData[0].length) return; // sometimes mismatched + + if (g.defDims == 0) { + for (var i = 0; i < g.rawData.length; i++) { + for (var j = 0; j < g.measures.length; j++) { + inData.push([ + {qElemNumber: -1, qNum: j+1, qText: g.measures[j]}, + g.rawData[i][j] + ]); + } + } + } + else if (g.defDims == 1 && g.defMeas > 1) { + for (var i = 0; i < g.rawData.length; i++) { + for (var j = 0; j < g.measures.length; j++) { + inData.push([ + g.rawData[i][0], + {qElemNumber: -1, qNum: j+1, qText: g.measures[j]}, + g.rawData[i][j+1] + ]); + } + } + } + else { + inData = g.rawData; // Process as in previous version + } + // If calculated color specified, must have provided Attribute + // otherwise set back to assigned mode + + var cx = g.defDims == 1 ? (g.defMeas == 1 ? 0 : 2) : 1; // index for attribute + if (g.colorSource == "C" && !inData[0][cx].qAttrExps) g.colorSource = "A"; + + // Function to get dimension/measure attribute for color + var cf = function(e) { + var cn = 0; + if (g.colorSource == "C") { + var cv = e[cx].qAttrExps.qValues[0].qNum; + if (!isNaN(cv)) + cn = Math.floor(Math.abs(cv)); + if (g.colorAttr == "C") { + // if text attribute exists, use it + if (e[cx].qAttrExps.qValues[0].qText) { + cn = e[cx].qAttrExps.qValues[0].qText; + } + else { + // number present, convert to hex + cv = cv.toString(16); + if (cv.length > 6) cv = cv.substring(cv.length-6); + cn = "#" + "000000".substring(0,6-cv.length) + cv; + } + } + } + return cn; + } + + // Process one dimension data + if (inData[0].length == 2) { + g.nDims = 1; + g.normalized = false; + inData.forEach(function(d) { + struc.push({dim1: d[0].qText, offset: d[1].qNum}); + flatData.push({ + dim1: d[0].qText, + dim2: d[0].qText, + offset: 0, + qNum: d[1].qNum, + qText: d[1].qText, + qTextPct: "", + qElemNumber: d[0].qElemNumber + }); + if(q.indexOf(d[0].qText) == -1) { + q.push(d[0].qText); + r.push(cf(d)); + } + }); + g.data = struc; + g.flatData = flatData; + g.allDim2 = q; + g.allCol2 = r; + return; + }; + + // Process two dimensional data + g.nDims = 2; + + var p1 = "", p2, edges = [], b, p = []; + inData.forEach(function(d) { + var c2 = d[1].qText; + if (p.indexOf(d[0].qText) == -1) { + p.push(d[0].qText); + } + if (q.indexOf(d[1].qText) == -1) { + q.push(d[1].qText); + r.push(cf(d)); + } + if (d[0].qText != p1) { + p1 = d[0].qText; + } + else { + b = false; + for (var i = 0; i < edges.length; i++) { + if (edges[i][0] == p2 && edges[i][1] == c2) { + b = true; + break; + } + } + if (!b) { + edges.push([p2,c2]) + } + } + p2 = c2; + }); + // Topological sort will throw an error if inconsistent data (sorting by measure) + // Just ignore errors and use original sort order + var qs, ps, rs = []; + try { + ps = q.slice(); + qs = this.toposort(q,edges); + // Replicate qs order in r + for (var i = 0; i < ps.length; i++) { + rs.push(r[ps.indexOf(qs[i])]); + } + r = rs; + } + catch(err) { + qs = q; + } + q = qs; + + var n = d3.nest() + .key(function(d) {return d[0].qText}) + .key(function(d) {return d[1].qText}) + .entries(inData) + ; + // sort all nodes in order specified by q + n.forEach(function(d) { + d.values.sort(function(a,b) { + return ( q.indexOf(a.key) < q.indexOf(b.key) ? -1 + : ( q.indexOf(a.key) > q.indexOf(b.key) ? 1 : 0)); + }); + }); + // nest messes up dim1 sort order, sort by order specified in p + n.sort(function(a,b) { + return ( p.indexOf(a.key) < p.indexOf(b.key) ? -1 + : ( p.indexOf(a.key) > p.indexOf(b.key) ? 1 : 0)); + }); + n.forEach(function(d, idx) { + var t = 0, v = [], j = 0, num, txt; + for (var i = 0; i < q.length; i++) { + if (d.values.length <= j || d.values[j].key != q[i]) { + num = 0; + txt = "-"; + elm = []; + } + else { + num = d.values[j].values[0][2].qNum; + txt = d.values[j].values[0][2].qText; + elm = d.values[j].values[0][0].qElemNumber; + j++; + } + v.push({ + dim2: q[i], + qNum: num, + qText: txt, + qElemNumber: elm, + offset: t + }); + t += num; + }; + v.forEach(function(e) { + e.dim1 = d.key; + if (g.normalized) { + e.offset = e.offset/t; + e.qNum = e.qNum/t; + e.qTextPct = d3.format(".1%")(e.qNum); + } + }); + flatData.push.apply(flatData,v); + struc.push({dim1: d.key, offset: t, values: v}); + + if (idx > 0 && g.showDeltas) { + var p = struc[idx-1].values; + var c = struc[idx].values; + for (var k = 0; k < p.length; k++) { + deltas.push({ + dim1p: p[k].dim1, + dim1c: c[k].dim1, + dim2: p[k].dim2, + delta: c[k].qNum - p[k].qNum, + deltaPct: 0, + points: [ + p[k].offset, + c[k].offset, + p[k].qNum, + c[k].qNum + ] + }); + } + } + + }); + g.data = struc; + g.flatData = flatData; + g.allDim2 = q; + g.allCol2 = r; + g.deltas = deltas; +}, + +/** + *-------------------------------------- + * Initialize Chart + *-------------------------------------- + * Set up initial elements, create axes, create legend + * create bars, deltas and legend items and bind data +*/ +initChart: function() { + + var g = this; + + var xLabelTitle = g.orientation == "V" ? g.labelTitleD : g.labelTitleM; + g.xAxisHeight = xLabelTitle == "B" || xLabelTitle == "L" + ? [70,40,25]["WMN".indexOf(g.orientation == "V" ? g.axisMarginD : g.axisMarginM)] : 0; + var xTitleHeight = xLabelTitle == "B" || xLabelTitle == "T" ? 20 : 0; + var xAxisPad = 20; + + var yLabelTitle = g.orientation == "V" ? g.labelTitleM : g.labelTitleD; + g.yAxisWidth = yLabelTitle == "B" || yLabelTitle == "L" + ? [90,50,30]["WMN".indexOf(g.orientation == "V" ? g.axisMarginM : g.axisMarginD)] : 0; + var yTitleWidth = yLabelTitle == "B" || yLabelTitle == "T" ? 20 : 0; + var yAxisPad = 20; + + var tr; // translate string + var dTitleHeight = g.labelTitleD == "B" || g.labelTitleD == "T" ? 20 : 0; + var margin = { + top: 10, //yAxisPad, + right: xAxisPad, + bottom: g.xAxisHeight + xTitleHeight + xAxisPad, + left: g.yAxisWidth + yTitleWidth + yAxisPad + }; + var innerWidth = g.width - margin.left - margin.right; + var innerHeight = g.height - margin.top - margin.bottom; + + g.lgn = { + minDim : [200,100], // min inner dimensions for legend to be displayed + use : "", + pad : 0, + sep : 5, + box : [12,12], // legend item color box + itmHeight : 20, + } + g.lgn.txtOff = g.lgn.box[0] + g.lgn.pad + g.lgn.sep; + + // adjust for legend if any + g.lgn.use = g.showLegend ? g.legendPosition : ""; + if (g.lgn.use) { + if (g.lgn.use == "L" || g.lgn.use == "R") { + if (innerWidth <= g.lgn.minDim[0]) { + g.lgn.use = ""; + } + else { + g.lgn.width = innerWidth/([4,6,10]["WMN".indexOf(g.legendSize)]); + innerWidth -= (g.lgn.width + yAxisPad); + g.lgn.height = innerHeight + g.xAxisHeight + xTitleHeight; + g.lgn.y = margin.top; + g.lgn.txtWidth = g.lgn.width - g.lgn.pad - g.lgn.sep - g.lgn.box[0]; + if (g.lgn.use == "L") { + g.lgn.x = yAxisPad; + margin.left += g.lgn.width + g.lgn.x; + } + else { + g.lgn.x = margin.left + innerWidth + yAxisPad; + } + } + } + else if (g.lgn.use == "T" || g.lgn.use == "B") { + if (innerHeight <= g.lgn.minDim[1]) { + g.lgn.use = ""; + } + else { + g.lgn.width = innerWidth + g.yAxisWidth + yTitleWidth; + g.lgn.height = g.lgn.itmHeight * (3 - "WMN".indexOf(g.legendSize)); + innerHeight -= g.lgn.height; + g.lgn.x = yAxisPad; + g.lgn.txtWidth = [100,75,50]["WMN".indexOf(g.legendSpacing)]; + if (g.lgn.use == "T") { + g.lgn.y = margin.top; + margin.top += g.lgn.height + } + else { + g.lgn.y = margin.bottom + innerHeight; + innerHeight -= 10; + } + } + } + } + g.component.selectAll("*") + .remove() + ; + var tooltip = g.component.append("div") + .attr("class","ldwtooltip") + .style("opacity","0") + ; + tooltip.append("p") + .attr("class","ldwttheading") + ; + tooltip.append("p") + .attr("class","ldwttvalue") + ; + g.svg = g.component + .append("svg") + .attr("width",g.width) + .attr("height",g.height) + .style("background-color",g.backgroundColor) + .append("g") + .attr("transform","translate(" + margin.left + "," + margin.top + ")") + ; + var dim1 = g.data.map(function(d) { return d.dim1; }); + if (g.orientation == "H") dim1.reverse() + g.dScale = d3.scale.ordinal() + .domain(dim1) + .rangeRoundBands(g.orientation == "V" ? [0, innerWidth] : [innerHeight,0], g.barGap, g.outerGap) + ; + g.mScale = d3.scale.linear() + .domain([0, d3.max(g.data, function(d) { return (g.normalized ? 1 : d.offset)*g.gridHeight; })]) + .range(g.orientation == "V" ? [innerHeight,0] : [0, innerWidth]) + .nice() + ; + var dGrp = g.svg.append("g") + .attr("class", "ldw-d ldwaxis"); + if (g.orientation == "V") { + dGrp.attr("transform", "translate(0," + innerHeight + ")"); + } + if (g.labelTitleD == 'B' || g.labelTitleD == 'L') { + g.dAxis = d3.svg.axis() + .scale(g.dScale) + .orient(g.orientation == "V" ? "bottom" : "left") + .tickSize(g.gridlinesD ? (g.orientation == "V" ? -innerHeight : -innerWidth) : 6) + .tickPadding(5) + ; + dGrp.call(g.dAxis); + } + if (g.labelTitleD == 'B' || g.labelTitleD == 'T') { + if(g.orientation == "V") { + tr = "translate(" + (innerWidth/2) + "," + (g.xAxisHeight + xTitleHeight) + ")"; + } + else { + tr = "translate(-" + (g.yAxisWidth + yTitleWidth/2 + 2) + "," + (innerHeight/2) + ")rotate(-90)"; + } + dGrp.append("text") + .attr("class","axisTitle") + .attr("text-anchor","middle") + .attr("transform",tr) + .text(g.axisTitleD) + ; + } + var mGrp = g.svg.append("g") + .attr("class", "ldw-m ldwaxis") + ; + if (g.orientation != "V") { + mGrp.attr("transform", "translate(0," + innerHeight + ")"); + } + if (g.labelTitleM == 'B' || g.labelTitleM == 'L') { + g.mAxis = d3.svg.axis() + .scale(g.mScale) + .orient(g.orientation == "V" ? "left" : "bottom") + .tickSize(g.gridlinesM ? (g.orientation == "V" ? -innerWidth : -innerHeight) : 6) + .ticks(g.ticks) + .tickFormat(d3.format([",.3s", ",.0f", ",.0%", ",.3s", g.axisFormatMs]["ANPSC".indexOf(g.axisFormatM)])) + .tickPadding(5) + ; + mGrp.call(g.mAxis); + } + if (g.labelTitleM == 'B' || g.labelTitleM == 'T') { + if(g.orientation == "V") { + tr = "translate(-" + (g.yAxisWidth + yTitleWidth/2 + 2) + "," + (innerHeight/2) + ")rotate(-90)"; + } + else { + tr = "translate(" + (innerWidth/2) + "," + (g.xAxisHeight + xTitleHeight) + ")"; + } + mGrp.append("text") + .attr("class","axisTitle") + .attr("text-anchor","middle") + .attr("transform",tr) + .text(g.axisTitleM) + ; + } + if (Object.getPrototypeOf(this).hasOwnProperty(g.colorScheme)) + ca = this[g.colorScheme]; + else + ca = d3.scale[g.colorScheme]().range(); + g.colorOffset %= ca.length; + // define various ways to get color + if (g.nDims == 1 && g.colorSource != "C" && g.singleColor) { + var rc = ca[g.colorOffset]; + g.cScale = function(d) { return rc; }; + } + else if (g.colorSource == "C") { + if (g.colorAttr == "O") { // color from calculated offset + g.cScale = function(d) { + return ca[g.allCol2[g.allDim2.indexOf(d)] % ca.length]; + } + } + else { // color is direct value + g.cScale = function(d) { + return g.allCol2[g.allDim2.indexOf(d)]; + } + } + } + else { + g.cScale = d3.scale.ordinal().range( + ca.slice(g.colorOffset,ca.length).concat(ca.slice(0,ca.length)) + ).domain(g.allDim2); + } + + // Create Legend + if (g.lgn.use) { + var lgn = d3.select("#" + g.id + " svg") + .append("g") + .attr("class","ldwlegend") + .attr("clip-path","url(#" + g.id + "_lgnClip)") + .attr("transform","translate(" + g.lgn.x + "," + g.lgn.y + ")") + ; + lgn.append("rect") + .attr("x",0) + .attr("y",0) + .attr("width",g.lgn.width) + .attr("height",g.lgn.height) + .style("fill",g.backgroundColor) + ; + lgn.append("clipPath") + .attr("id",g.id + "_lgnClip") + .append("rect") + .attr("x","0") + .attr("y","0") + .attr("width",g.lgn.width) + .attr("height",g.lgn.height) + ; + lgn.append("g") + .attr("class","ldwlgnitems") + } + // Create bars + g.bars = g.svg.selectAll("#" + g.id + " .ldwbar") + .data(g.flatData, function(d) { return d.dim1 + '|' + d.dim2; } ) + ; + // Text on bars + if (g.showTexts != "N") { + // Create text box for determining sizing + g.tref = g.svg.append("text") + .attr("x","0") + .attr("y","-100") + .attr("class","ldwtxtref") + ; + + if (~"TA".indexOf(g.showTexts) && !g.normalized) { + // Create bars totals + g.totals = g.svg.selectAll("#" + g.id + " .ldwtot") + .data(g.data, function(d) { return d.dim1; } ) + ; + } + if (~"BA".indexOf(g.showTexts)) { + // Create text on bars + g.texts = g.svg.selectAll("#" + g.id + " .ldwtxt") + .data(g.flatData, function(d) { return d.dim1 + '|' + d.dim2; } ) + ; + } + } + // Create deltas + if (g.showDeltas && g.nDims == 2) { + g.polys = g.svg.selectAll("#" + g.id + " polygon") + .data(g.deltas, function(d) { return d.dim1p + "-" + d.dim1c + "," + d.dim2; } ) + ; + } + // Create legend items + if (g.lgn.use) { + g.lgn.items = d3.select("#" + g.id + " .ldwlgnitems") + .selectAll("g") + .data(g.allDim2) + ; + } + +}, + +/** + *-------------------------------------- + * Create Bars + *-------------------------------------- + * Set up initial properties of bars, deltas, and legend + * Objects already have had data bound + * This procedure is also called from updateBars to add new items +*/ +createBars: function() { + + var g = this; + // Create bars + g.bars + .enter() + .append("rect") + .attr("ldwdim1", function(d) { return d.qElemNumber; }) + .attr(g.orientation == "V" ? "x" : "y", function(d) { return g.dScale(d.dim1); }) + .attr(g.orientation == "V" ? "y" : "x", function(d) { return g.mScale(0); }) // grow from bottom +// .attr(g.orientation == "V" ? "y" : "x", function(d) { return g.mScale(d.offset); }) // venetian blinds + .attr(g.orientation == "V" ? "width" : "height", g.dScale.rangeBand()) + .attr(g.orientation == "V" ? "height" : "width", function(d) { return 0; }) + .style("fill", function(d) { return g.cScale(d.dim2); }) + .style("opacity","0") + .attr("class","selectable ldwbar") + .on("click",function(d) { + if (d.qElemNumber >= 0) { // Cannot select a measure + if (g.selectionMode == "QUICK") { + g.self.backendApi.selectValues(0,[d.qElemNumber],true); + } + else if (g.selectionMode == "CONFIRM") { + var t = d3.select(this).classed("selected"); + g.self.selectValues(0,[d.qElemNumber],true); + // following to address QS bug where clear button does not clear class names + g.self.clearSelectedValues = function() { + d3.selectAll("#" + g.id +" .selected").classed("selected",false); + }; + var x = d3.selectAll("#" + g.id + " [ldwdim1='" + d.qElemNumber + "']") + .classed("selected",!t); + d3.select("#" + g.id + " .ldwtooltip") + .style("opacity","0") + .transition() + .remove + ; + } + } + return; + }) + .on("mouseenter",function(d) { + if (g.inSelections || g.editMode) return; + d3.select(this) + .style("opacity","0.5") + .attr("stroke","white") + .attr("stroke-width","2") + ; + // Place text in tooltip + d3.select("#" + g.id + " .ldwttheading") + .text(g.nDims == 2 ? d.dim1 + ", " + d.dim2 : d.dim1); + d3.select("#" + g.id + " .ldwttvalue") + .text(g.nDims == 2 ? + (g.normalized ? d.qTextPct + ", " + d.qText : d.qText) + : d.qText); + + var matrix = this.getScreenCTM() + .translate(+this.getAttribute("x"),+this.getAttribute("y")); + + var xPosition = (window.pageXOffset + matrix.e) + - d3.select("#" + g.id + " .ldwtooltip")[0][0].clientWidth/2 + + (g.orientation == "V" ? g.dScale.rangeBand() : d3.select(this).attr("width"))/2 + ; + var yPosition = (window.pageYOffset + matrix.f) + - d3.select("#" + g.id + " .ldwtooltip")[0][0].clientHeight + -10 + ; + d3.select("#" + g.id + " .ldwtooltip") + .style("left", xPosition + "px") + .style("top", yPosition + "px") + .transition() + .delay(750) + .style("opacity","0.95") + ; + }) + .on("mouseleave",function() { + d3.select(this) + .style("opacity","1.0") + .attr("stroke","none") + ; + d3.select("#" + g.id + " .ldwtooltip") + .style("opacity","0") + .transition() + .remove + ; + }) + ; + + if (~"TA".indexOf(g.showTexts) && !g.normalized) { + // Create totals + g.totals + .enter() + .append("text") + .attr("class","ldwtot") + .style("opacity","0") + .each(function(d) { + d.qNum = g.mScale.domain()[1] - d.offset; + d.qText = d3.format([",.0f", ",.0%", ",.3s", g.totalFormatMs]["NPSC".indexOf(g.totalFormatM)])(d.offset); + var txp = g.barText(d, true); + + d3.select(this) + .style("fill", "black") + .style("font-size", g.tref.style("font-size")) + .attr("x",g.orientation == "V" ? txp.x : 0) + .attr("y",g.orientation == "V" ? g.mScale(0) : txp.y) + .attr("dy","-.2em") + .text(txp.text) + ; + + }) + ; + } + + if (~"BA".indexOf(g.showTexts)) { + // Create text inside bars + g.texts + .enter() + .append("text") + .attr("class","ldwtxt") + .style("opacity","0") + .each(function(d) { + var txp = g.barText(d); + + d3.select(this) + .style("fill", g.textColor == "Auto" ? g.txtColor(g.cScale(d.dim2)) : g.textColor ) + .style("font-size", g.tref.style("font-size")) + .attr("x",g.orientation == "V" ? txp.x : 0) + .attr("y",g.orientation == "V" ? g.mScale(0) : txp.y) + .attr("dy","-.2em") + .text(txp.text) + ; + + }) + ; + } + + if (g.showDeltas && g.nDims == 2) { + + // Create deltas + g.polys + .enter() + .append("polygon") + .attr("points",function(d) { + var p; + if (g.orientation == "V") { + p = (g.dScale(d.dim1p)+g.dScale.rangeBand()) + "," + + g.mScale(0) + " " + + (g.dScale(d.dim1p)+g.dScale.rangeBand()) + "," + + g.mScale(0) + " " + + (g.dScale(d.dim1c)) + "," + + g.mScale(0) + " " + + (g.dScale(d.dim1c)) + "," + + g.mScale(0) + ; + } + else { + p = g.mScale(0) + "," + (g.dScale(d.dim1p) + g.dScale.rangeBand()) + " " + + g.mScale(0) + "," + (g.dScale(d.dim1p) + g.dScale.rangeBand()) + " " + + g.mScale(0) + "," + g.dScale(d.dim1c) + " " + + g.mScale(0) + "," + g.dScale(d.dim1c) + ; + } + return p; + }) + .style("fill",function(d) { + return g.cScale(d.dim2); + }) + .style("opacity","0") + .on("mouseenter",function(d) { + var pt = this.getAttribute("points").split(" "); + var sx = 0, sy = 0; + pt.forEach(function(e, i) { + var x = e.split(","); + if (g.orientation == "H") { + if (i < 2) { + sx += parseFloat(x[0]); + sy += parseFloat(x[1]); + } + } + else if (i == 0 || i == 3) { + sx += parseFloat(x[0]); + sy += parseFloat(x[1]); + } + } ); + sx /= 2; + sy /= 2; + + if (g.inSelections || g.editMode) return; + + d3.select(this) + .style("opacity","0.5") + .attr("stroke","white") + .attr("stroke-width","2"); + // Place text in tooltip + d3.select("#" + g.id + " .ldwttheading") + .text(d.dim2 + ", " + d.dim1p + "-" + d.dim1c); + d3.select("#" + g.id + " .ldwttvalue") + .text(d3.format(g.normalized ? "+.1%" : "+.3s")(d.delta)); + + var matrix = this.getScreenCTM() + .translate(sx,sy); + + var xPosition = (window.pageXOffset + matrix.e) + - d3.select("#" + g.id + " .ldwtooltip")[0][0].clientWidth/2 + ; + var yPosition = (window.pageYOffset + matrix.f) + - d3.select("#" + g.id + " .ldwtooltip")[0][0].clientHeight + -10 + ; + d3.select("#" + g.id + " .ldwtooltip") + .style("left", xPosition + "px") + .style("top", yPosition + "px") + .transition() + .delay(750) + .style("opacity","0.95") + ; + }) + .on("mouseleave",function() { + d3.select(this) + .style("opacity",g.barGap == 1 ? "1" : "0.5") + .attr("stroke","none") + ; + d3.select("#" + g.id + " .ldwtooltip") + .style("opacity","0") + .transition() + .remove; + }) + ; + } + // create legend + if (g.lgn.use) { + g.lgn.items + .enter() + .append("g") + .attr("class","ldwlgnitem") + .each(function(d, i) { + d3.select(this) + .append("rect") +// .attr("x","0") // Initialize to zero to have legend grow from top +// .attr("y","0") + .attr("x",function(e) { + var x; + if (g.lgn.use == "T" || g.lgn.use == "B") { + x = i*(g.lgn.txtOff + g.lgn.txtWidth); + } + else { + x = g.lgn.pad; + } + return x; + }) + .attr("y",function(e) { + var y; + if (g.lgn.use == "T" || g.lgn.use == "B") { + y = g.lgn.pad; + } + else { + y = g.lgn.pad + g.lgn.itmHeight*i; + } + return y; + }) + .style("opacity","0") + .attr("width",g.lgn.box[0]) + .attr("height",g.lgn.box[1]) + .style("fill",function(e) { return g.cScale(e); }) + ; + d3.select(this) + .append("text") +// .attr("x","0") // Initialize to zero to have legend grow from top +// .attr("y","0") + .attr("x",function(e) { + var x; + if (g.lgn.use == "T" || g.lgn.use == "B") { + x = i*(g.lgn.txtOff + g.lgn.txtWidth) + g.lgn.txtOff; + } + else { + x = g.lgn.txtOff; + } + return x; + }) + .attr("y",function(e) { + var y; + if (g.lgn.use == "T" || g.lgn.use == "B") { + y = g.lgn.pad + 11; + } + else { + y = g.lgn.pad + g.lgn.itmHeight*i + 11; + } + return y; + }) + .style("opacity","0") + .text(function(e) { + return e; + }) + ; + }) + ; + } +}, +/** + *-------------------------------------- + * Bar Text + *-------------------------------------- + * Get bar text information: x, y and text +*/ +barText: function(d, total) { + var tx, txt, origX, bHeight, textX, bb, textY, textLength, ts; + var g = this; + + // Relative text sizing, relative to bar width + // For total, make larger by reducing unneeded padding + var hAlign = g.hAlign, vAlign = g.vAlign, + innerBarPadV = +g.innerBarPadV, + innerBarPadH = +g.innerBarPadH; + if (g.textSizeAbs) + ts = g.textSize; + else { + if (total) + if (g.orientation == "H") + innerBarPadV = 1; + else + innerBarPadH = 1; + ts = g.textSizeFactor * (g.dScale.rangeBand() - 2 * (g.orientation == "H" ? innerBarPadV : innerBarPadH)) + // limit size to maximum, also stored in textSize + if (ts > g.textSize) ts = g.textSize; + } + g.tref.style("font-size", ts); + + if (total) { // Override some alignment for total + if (g.orientation == "V") + vAlign = "B" + else + hAlign = "L" + } + origX = g.orientation == "V" ? g.dScale(d.dim1) : g.mScale(d.offset); + bHeight = g.orientation == "V" ? g.mScale(0) - g.mScale(d.qNum) : g.dScale.rangeBand(); + textX = g.orientation == "V" ? g.dScale.rangeBand() : g.mScale(d.qNum); + + g.tref.text(d.qNum == 0 ? "" + : (total + ? (g.showTot == "D" ? d.dim1 : d.qText) + : (g.showDim == "D" ? d.dim2 : (g.showDim == "P" && g.normalized ? d.qTextPct : d.qText)) + ) + ); + + bb = g.tref.node().getBBox(); + tx = origX + innerBarPadH; + if (vAlign == "C") + textY = g.orientation == "V" ? g.mScale(d.offset) - (g.mScale(0) - g.mScale(d.qNum)) + + (g.mScale(0) - g.mScale(d.qNum) + bb.height)/2 + : g.dScale(d.dim1) + (g.dScale.rangeBand() + bb.height)/2; + else if (vAlign == "T") + textY = g.orientation == "V" ? g.mScale(d.offset) - (g.mScale(0) - g.mScale(d.qNum)) + + bb.height + innerBarPadV + : g.dScale(d.dim1) + innerBarPadV + bb.height; + else + textY = g.orientation == "V" ? g.mScale(d.offset) - innerBarPadV + : g.dScale(d.dim1) + g.dScale.rangeBand() - innerBarPadV; + txt = ""; + if (bb.height + 2 * innerBarPadV <= bHeight || (g.orientation != "V" && !g.textSizeAbs)) { + if (bb.width + 2 * innerBarPadH <= textX) { + if (hAlign == "C") { + tx = origX + (textX - bb.width)/2; + } + else if (hAlign == "R") { + tx = origX + textX - bb.width - innerBarPadH; + } + txt = g.tref.text(); + } + else if (g.textDots) { + textLength = g.tref.node().getComputedTextLength(); + txt = g.tref.text(); + while (textLength > textX - 2 * innerBarPadH && txt.length > 0) { + txt = txt.slice(0, -1); + g.tref.text(txt + '\u2026'); + textLength = g.tref.node().getComputedTextLength(); + } + if (txt.length != 0) txt = g.tref.text(); + } + } + return {x: tx, y: textY, text: txt}; + +}, +/** + *-------------------------------------- + * Update Bars + *-------------------------------------- + * Modify properties of bars, deltas, and legend +*/ +updateBars: function() { + + var g = this; + + var dim1 = g.data.map(function(d) { return d.dim1; }); + if (g.orientation == "H") dim1.reverse() + g.dScale.domain(dim1); + g.mScale.domain( [0, d3.max(g.data, function(d) { return (g.normalized ? 1 : d.offset)*g.gridHeight; })]); + var tDelay = g.transitions && !g.editMode ? g.transitionDelay : 0; + var tDuration = g.transitions && !g.editMode ? g.transitionDuration : 0; + + // Procedure to update dimension and measure axes + var updateAxis = function (labelTitle, labelStyle, axis, axisClass, isXAxis, axisWidth) { + + if (labelTitle == 'B' || labelTitle == 'L') { + // Update axis with transition + g.svg.select("#" + g.id + " ." + axisClass + ".ldwaxis") + .transition() + .delay(tDelay) + .duration(tDuration) + .ease(g.ease) + .call(axis) + ; + var lbl = d3.selectAll("#" + g.id + " ." + axisClass + ".ldwaxis"); + var txt = lbl.selectAll(".tick.major text") + .attr("transform",null); // All horizontal initially + var maxWidth = isXAxis ? lbl.node().getBBox().width/txt[0].length : g.yAxisWidth - 5; + + // If auto labels and any overlap, set to tilted + if (labelStyle == "A") { + txt.each(function(d, i) { + if (d3.select(this).node().getComputedTextLength() > maxWidth) { + labelStyle = "T" // no break for each + } + }) + ; + } + // Tilted labels + if (labelStyle == "T") { + txt.style("text-anchor","end") + .attr("transform","translate(" + (isXAxis ? "-12,0" : "-2,-8") + ") rotate(-45)") + ; + maxWidth = isXAxis ? g.xAxisHeight - 5 : maxWidth * Math.sqrt(2); + } + // Staggered labels + else if (labelStyle == "S" && isXAxis) { + txt.each(function(d, i) { + if (i % 2 == 1) { + d3.select(this).attr("transform","translate(0,14)"); + } + }) + ; + } + // Horizontal or titled labels, use ellipsis if overlap + if (labelStyle == "H" || labelStyle == "T") { + txt.each(function(d, i) { + var self = d3.select(this), + textLength = self.node().getComputedTextLength(), + text = self.text(); + while (textLength > maxWidth && text.length > 0) { + text = text.slice(0, -1); + self.text(text + '\u2026'); + textLength = self.node().getComputedTextLength(); + } + }); + } + } + }; + // Update dimension axis + updateAxis(g.labelTitleD, g.labelStyleD, g.dAxis, "ldw-d", g.orientation == "V"); + // Update measure axis + updateAxis(g.labelTitleM, g.labelStyleM, g.mAxis, "ldw-m", g.orientation != "V"); + + g.bars = g.svg.selectAll("#" + g.id + " .ldwbar") + .data(g.flatData, function(d) { return d.dim1 + '|' + d.dim2; } ) + ; + // Remove bars with transition + g.bars + .exit() + .transition() + .delay(tDelay) + .duration(tDuration) + .ease(g.ease) + .style("opacity", "0") + .remove() + ; + + // Remove totals with transition + if (~"TA".indexOf(g.showTexts)) { + g.totals = g.svg.selectAll("#" + g.id + " .ldwtot") + .data(g.data, function(d) { return d.dim1; } ) + ; + g.totals + .exit() + .transition() + .delay(tDelay) + .duration(tDuration) + .ease(g.ease) + .style("opacity", "0") + .remove() + ; + } + // Remove texts with transition + if (~"BA".indexOf(g.showTexts)) { + g.texts = g.svg.selectAll("#" + g.id + " .ldwtxt") + .data(g.flatData, function(d) { return d.dim1 + '|' + d.dim2; } ) + ; + g.texts + .exit() + .transition() + .delay(tDelay) + .duration(tDuration) + .ease(g.ease) + .style("opacity", "0") + .remove() + ; + } + + if (g.showDeltas && g.nDims == 2) { + g.polys = g.svg.selectAll("#" + g.id + " polygon") + .data(g.deltas, function(d) { return d.dim1p + "-" + d.dim1c + "," + d.dim2; } ) + ; + // Remove deltas with transition + g.polys + .exit() + .transition() + .delay(tDelay) + .duration(tDuration) + .ease(g.ease) + .style("opacity", "0") + .remove() + ; + } + // remove legend items with transition + if (g.lgn.use) { + g.lgn.items = d3.selectAll("#" + g.id + " .ldwlgnitems") + .selectAll("g") + .data(g.allDim2, function(d) { return d; }) + ; + g.lgn.items + .exit() + .transition() + .delay(tDelay) + .duration(tDuration) + .ease(g.ease) + .style("opacity", "0") + .remove() + ; + } + + // Add any new bars/deltas/legend items + this.createBars(); + + // Update bars + if (g.orientation == "V") { + g.bars.transition() + .delay(tDelay) + .duration(tDuration) + .ease(g.ease) + .style("opacity","1") + .attr("x", function(d, i) { + return g.dScale(d.dim1) ? g.dScale(d.dim1) : 0; // ignore NaN: causing errors in transitions + }) + .attr("y", function(d) { + return g.mScale(d.offset) - (g.mScale(0) - g.mScale(d.qNum)); + }) + .attr("width", g.dScale.rangeBand() && g.dScale.rangeBand() > 0 ? g.dScale.rangeBand() : 0) // ignore NaN: causing errors in transitions + .attr("height", function(d) { + return g.mScale(0) > g.mScale(d.qNum) ? g.mScale(0) - g.mScale(d.qNum) : 0; // ignore negatives: causing errors in transitions + }) + ; + } + else { + g.bars.transition() + .delay(tDelay) + .duration(tDuration) + .ease(g.ease) + .style("opacity","1") + .attr("x", function(d) { + return g.mScale(d.offset); + }) + .attr("y", function(d, i) { + return g.dScale(d.dim1); + }) + .attr("width", function(d) { + return g.mScale(d.qNum); + }) + .attr("height", g.dScale.rangeBand()) + ; + } + + if (~"TA".indexOf(g.showTexts) && !g.normalized) { + // Update totals + g.totals + .each(function(d) { + d.qNum = g.mScale.domain()[1] - d.offset; + d.qText = d3.format([",.0f", ",.0%", ",.3s", g.totalFormatMs]["NPSC".indexOf(g.totalFormatM)])(d.offset); + var txp = g.barText(d, true); + d3.select(this) + .transition() + .delay(tDelay) + .duration(tDuration) + .ease(g.ease) + .style("opacity","1") + .style("fill", "black") + .style("font-size", g.tref.style("font-size")) + .attr({x: txp.x, y: txp.y, dy: "-.2em"}) + .text(txp.text) + ; + + }) + ; + } + + if (~"BA".indexOf(g.showTexts)) { + // Update texts + g.texts + .each(function(d) { + var txp = g.barText(d); + + d3.select(this) + .transition() + .delay(tDelay) + .duration(tDuration) + .ease(g.ease) + .style("opacity","1") + .style("fill", g.textColor == "Auto" ? g.txtColor(g.cScale(d.dim2)) : g.textColor ) + .style("font-size", g.tref.style("font-size")) + .attr({x: txp.x, y: txp.y, dy: "-.2em"}) + .text(txp.text) + ; + + }) + ; + } + + if (g.showDeltas && g.nDims == 2) { + + // update deltas + g.polys.transition() + .delay(tDelay) + .duration(tDuration) + .ease(g.ease) + .attr("points",function(d) { + var p; + if (g.orientation == "V") { + p = (g.dScale(d.dim1p)+g.dScale.rangeBand()) + "," + + (g.mScale(d.points[0]) - (g.mScale(0) - g.mScale(d.points[2]))) + " " + + + (g.dScale(d.dim1p)+g.dScale.rangeBand()) + "," + + g.mScale(d.points[0]) + " " + + + (g.dScale(d.dim1c)) + "," + + g.mScale(d.points[1]) + " " + + + (g.dScale(d.dim1c)) + "," + + (g.mScale(d.points[1]) - (g.mScale(0) - g.mScale(d.points[3]))) + ; + } + else { + p = (g.mScale(d.points[0]) + g.mScale(d.points[2])) + "," + + (g.dScale(d.dim1p) + g.dScale.rangeBand()) + " " + + + g.mScale(d.points[0]) + "," + + (g.dScale(d.dim1p) + g.dScale.rangeBand()) + " " + + + g.mScale(d.points[1]) + "," + + g.dScale(d.dim1c) + " " + + + (g.mScale(d.points[1]) + g.mScale(d.points[3])) + "," + + g.dScale(d.dim1c) + ; + } + return p; + }) + .style("fill",function(d) { + return g.cScale(d.dim2); + }) + .style("opacity",g.barGap == 1 ? "1" : "0.5") + ; + } + // update legend items + if (g.lgn.use) { + + if (g.lgn.use == "T" || g.lgn.use == "B") { + var maxprow = Math.floor(g.lgn.width/(g.lgn.txtOff + g.lgn.txtWidth)); + var nprow = maxprow; + } + + g.lgn.items + .each(function(d, i) { + d3.select(this) + .transition() + .delay(tDelay) + .duration(tDuration) + .select("rect") + .attr("x",function(e) { + var x; + if (g.lgn.use == "T" || g.lgn.use == "B") { + x = (i % nprow)*(g.lgn.txtOff + g.lgn.txtWidth); + } + else { + x = g.lgn.pad; + } + return x; + }) + .attr("y",function(e) { + var y; + if (g.lgn.use == "T" || g.lgn.use == "B") { + y = g.lgn.pad + Math.floor(i/nprow) * g.lgn.itmHeight; + } + else { + y = g.lgn.pad + g.lgn.itmHeight*i; + } + return y; + }) + .style("opacity","1") + ; + var txt = d3.select(this) + .transition() + .delay(tDelay) + .duration(tDuration) + .select("text") + .attr("x",function(e) { + var x; + if (g.lgn.use == "T" || g.lgn.use == "B") { + x = (i % nprow)*(g.lgn.txtOff + g.lgn.txtWidth) + g.lgn.txtOff; + } + else { + x = g.lgn.txtOff; + } + return x; + }) + .attr("y",function(e) { + var y; + if (g.lgn.use == "T" || g.lgn.use == "B") { + y = g.lgn.pad + Math.floor(i/nprow) * g.lgn.itmHeight + 11; + } + else { + y = g.lgn.pad + g.lgn.itmHeight*i + 11; + } + return y; + }) + .style("opacity","1") + ; + txt.each(function(d, i) { + var self = d3.select(this), + textLength = self.node().getComputedTextLength(), + text = self.text(); + while (textLength > g.lgn.txtWidth && text.length > 0) { + text = text.slice(0, -1); + self.text(text + '\u2026'); + textLength = self.node().getComputedTextLength(); + } + }); + ; + }) + ; + } +}, + +/** + *-------------------------------------- + * Refresh chart + *-------------------------------------- + * Refresh chart, no new data +*/ +refreshChart: function() { + + this.initChart(); + this.createBars(); + this.updateBars(); +}, +//-------------------------------------- +// Topological sort +//-------------------------------------- +/*- begin https://github.com/marcelklehr/toposort */ +toposort: function(nodes, edges) { + var cursor = nodes.length + , sorted = new Array(cursor) + , visited = {} + , i = cursor + + while (i--) { + if (!visited[i]) visit(nodes[i], i, []) + } + + return sorted + + function visit(node, i, predecessors) { + if(predecessors.indexOf(node) >= 0) { + throw new Error('Cyclic dependency: '+JSON.stringify(node)) + } + +// if (!~nodes.indexOf(node)) { +// throw new Error('Found unknown node. Make sure to provided all involved nodes. Unknown node: '+JSON.stringify(node)) +// } + + if (visited[i]) return; + visited[i] = true + + // outgoing edges + var outgoing = edges.filter(function(edge){ + return edge[0] === node + }) + if (i = outgoing.length) { + var preds = predecessors.concat(node) + do { + var child = outgoing[--i][1] + visit(child, nodes.indexOf(child), preds) + } while (i) + } + + sorted[--cursor] = node + } +}, +/*- end https://github.com/marcelklehr/toposort */ +/*- begin http://stackoverflow.com/questions/11867545 */ +txtColor: function(hexcolor){ + var r = parseInt(hexcolor.substr(1,2),16); + var g = parseInt(hexcolor.substr(3,2),16); + var b = parseInt(hexcolor.substr(5,2),16); + var yiq = ((r*299)+(g*587)+(b*114))/1000; + return (yiq >= 160) ? 'black' : 'white'; // 128 changed to 160 to give white preference +}, +/*- end http://stackoverflow.com/questions/11867545 */ +//-------------------------------------- +// Color Schemes +//-------------------------------------- +custom100: [ +"#99c867","#e43cd0","#e2402a","#66a8db","#3f1a20","#e5aa87","#3c6b59","#aa2a6b","#e9b02e","#7864dd", +"#65e93c","#5ce4ba","#d0e0da","#d796dd","#64487b","#e4e72b","#6f7330","#932834","#ae6c7d","#986717", +"#e3cb70","#408c1d","#dd325f","#533d1c","#2a3c54","#db7127","#72e3e2","#e2c1da","#d47555","#7d7f81", +"#54ae9b","#e9daa6","#3a8855","#5be66e","#ab39a4","#a6e332","#6c469d","#e39e51","#4f1c42","#273c1c", +"#aa972e","#8bb32a","#bdeca5","#63ec9b","#9c3519","#aaa484","#72256d","#4d749f","#9884df","#e590b8", +"#44b62b","#ad5792","#c65dea","#e670ca","#e38783","#29312d","#6a2c1e","#d7b1aa","#b1e7c3","#cdc134", +"#9ee764","#56b8ce","#2c6323","#65464a","#b1cfea","#3c7481","#3a4e96","#6493e1","#db5656","#747259", +"#bbabe4","#e33f92","#d0607d","#759f79","#9d6b5e","#8574ae","#7e304c","#ad8fac","#4b77de","#647e17", +"#b9c379","#8da8b0","#b972d9","#786279","#7ec07d","#916436","#2d274f","#dce680","#759748","#dae65a", +"#459c49","#b7934a","#51c671","#9ead3f","#969a5c","#b9976a","#46531a","#c0f084","#76c146","#bad0ad" +], +google20: [ +"#3366cc","#dc3912","#ff9900","#109618","#990099","#0099c6","#dd4477","#66aa00","#b82e2e","#316395", +"#994499","#22aa99","#aaaa11","#6633cc","#e67300","#8b0707","#651067","#329262","#5574a6","#3b3eac" +], +qlikView18: [ +"#8daacb","#fc7362","#bbd854","#ffd92f","#66c296","#e5b694","#e78ad2","#b3b3b3","#a6d8e3","#abe9bc", +"#1b7d9c","#ffbfc9","#4da741","#c4b2d6","#b22424","#00acac","#be6c2c","#695496" +], +qlikSense12: [ +"#332288","#6699cc","#88ccee","#44aa99","#117733","#999933","#ddcc77","#661100","#cc6677","#aa4466", +"#882255","#aa4499" +] } \ No newline at end of file diff --git a/lib/angular.js b/src/lib/angular.js similarity index 100% rename from lib/angular.js rename to src/lib/angular.js diff --git a/lib/d3.v3.js b/src/lib/d3.v3.js similarity index 100% rename from lib/d3.v3.js rename to src/lib/d3.v3.js diff --git a/lib/jquery.js b/src/lib/jquery.js similarity index 100% rename from lib/jquery.js rename to src/lib/jquery.js