From b631810d791bf2f845845b97961ad2ed0e83a339 Mon Sep 17 00:00:00 2001 From: charithag Date: Mon, 3 Aug 2015 17:34:23 +0530 Subject: [PATCH] Display statistics for grouped devices --- .../jaggeryapps/iotserver/api/stats-api.jag | 206 +- .../jaggeryapps/iotserver/modules/device.js | 4 +- .../pages/{groups => }/analytics.hbs | 2 +- .../iotserver/pages/devices/analytics.hbs | 9 - .../pages/{groups/index.hbs => groups.hbs} | 0 .../analiytics-graphs/analiytics-graphs.hbs | 2 + .../analiytics-graphs/analiytics-graphs.json | 2 +- .../analiytics-graphs/public/css/detail.css | 105 + .../analiytics-graphs/public/css/graph.css | 6 +- .../analiytics-graphs/public/css/legend.css | 73 + .../analiytics-graphs/public/css/lines.css | 5 +- .../iotserver/units/analytics/analytics.hbs | 12 +- .../units/analytics/public/js/Chart.min.js | 11 - .../units/analytics/public/js/bulb.js | 10 - .../units/analytics/public/js/date-range.js | 0 .../units/analytics/public/js/graph_util.js | 348 +- .../analytics/public/js/graphs/bulb_graph.js | 85 - .../analytics/public/js/graphs/cpu_graph.js | 69 - .../public/js/graphs/cpu_temperature_graph.js | 70 - .../analytics/public/js/graphs/fan_graph.js | 84 - .../analytics/public/js/graphs/light_graph.js | 85 - .../public/js/graphs/motion_graph.js | 70 - .../analytics/public/js/graphs/ram_graph.js | 69 - .../analytics/public/js/graphs/sonar_graph.js | 69 - .../public/js/graphs/temperature_graph.js | 70 - .../analytics/public/js/linewithfocuschart.js | 0 .../units/analytics/public/js/moment.js | 2808 ---- .../units/analytics/public/js/nv.d3.js | 13435 ---------------- .../analytics/public/js/stream_layers.js | 35 - .../units/device-detail/device-detail.hbs | 2 +- .../units/device-listing/device-listing.hbs | 2 +- .../units/group-detail/group-detail.hbs | 2 +- 32 files changed, 589 insertions(+), 17161 deletions(-) rename modules/distribution/src/repository/jaggeryapps/iotserver/pages/{groups => }/analytics.hbs (65%) delete mode 100644 modules/distribution/src/repository/jaggeryapps/iotserver/pages/devices/analytics.hbs rename modules/distribution/src/repository/jaggeryapps/iotserver/pages/{groups/index.hbs => groups.hbs} (100%) create mode 100644 modules/distribution/src/repository/jaggeryapps/iotserver/units/analiytics-graphs/public/css/detail.css create mode 100644 modules/distribution/src/repository/jaggeryapps/iotserver/units/analiytics-graphs/public/css/legend.css delete mode 100644 modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/Chart.min.js delete mode 100644 modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/bulb.js delete mode 100644 modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/date-range.js delete mode 100644 modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/bulb_graph.js delete mode 100644 modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/cpu_graph.js delete mode 100644 modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/cpu_temperature_graph.js delete mode 100644 modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/fan_graph.js delete mode 100644 modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/light_graph.js delete mode 100644 modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/motion_graph.js delete mode 100644 modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/ram_graph.js delete mode 100644 modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/sonar_graph.js delete mode 100644 modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/temperature_graph.js delete mode 100644 modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/linewithfocuschart.js delete mode 100644 modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/moment.js delete mode 100644 modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/nv.d3.js delete mode 100644 modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/stream_layers.js diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/api/stats-api.jag b/modules/distribution/src/repository/jaggeryapps/iotserver/api/stats-api.jag index 852a8589..27549d09 100644 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/api/stats-api.jag +++ b/modules/distribution/src/repository/jaggeryapps/iotserver/api/stats-api.jag @@ -25,102 +25,170 @@ var log = new Log("api/stats-api.jag"); var constants = require("/modules/constants.js"); var dcProps = require('/config/dc-props.js').config(); var utility = require("/modules/utility.js").utility; +var deviceModule = require("/modules/device.js").deviceModule; -var result; -var statsClient = new Packages.org.wso2.carbon.device.mgt.iot.common.analytics.statistics.IoTUsageStatisticsClient; +var from = request.getParameter("from"); +var to = request.getParameter("to"); + +var user = session.get(constants.USER_SESSION_KEY); +if (!user) { + response.sendRedirect(dcProps.appContext + "login?#login-required"); + exit(); +} +var carbon = require('carbon'); +var carbonHttpsServletTransport = carbon.server.address('https'); +var deviceCloudService = carbonHttpsServletTransport + "/devicecloud/group_manager"; + +var statsClient = new Packages.org.wso2.carbon.device.mgt.iot.common.analytics.statistics.IoTUsageStatisticsClient; +var stats = {}; +var deviceId; +var deviceType; if (uriMatcher.match("/{context}/api/stats")) { deviceId = request.getParameter("deviceId"); deviceType = request.getParameter("deviceType"); - from = request.getParameter("from"); - to = request.getParameter("to"); - - user = session.get(constants.USER_SESSION_KEY); - if (!user) { - response.sendRedirect(dcProps.appContext + "login?#login-required"); - exit(); - } log.info("deviceId : " + deviceId + " from : " + from + " to : " + to); - - switch (deviceType) { - case "firealarm": - result = getFireAlarmData(user.username, deviceId, from, to); - break; - case "sensebot": - result = getSensebotData(user.username, deviceId, from, to); - break; - case "arduino": - result = getArduinoData(user.username, deviceId, from, to); - break; - case "digital_display": - result = getDigitalDisplayData(user.username, deviceId, from, to); - break; - case "android_sense": - result = getAndroidSenseData(user.username, deviceId, from, to); - break; - case "raspberrypi": - result = getDigitalDisplayData(user.username, deviceId, from, to); - break; - default: - result = new Object(); - } + getDeviceData(deviceType, deviceId); + +}else if (uriMatcher.match("/{context}/api/stats/group")){ + var groupId = request.getParameter("groupId"); + + //URL: GET https://localhost:9443/devicecloud/group_manager/group/id/{groupId}/device/all + var endPoint = deviceCloudService + "/group/id/" + groupId + "/device/all"; + var data = {"username": user.username}; + log.info("ep: " + endPoint); + var devices = get(endPoint, data, "json"); + + for (var device in devices.data){ + deviceId = devices.data[device].deviceIdentifier; + deviceType = devices.data[device].type; + getDeviceData(deviceType, deviceId); + } } // returning the result. -if (result) { - print(result); +if (stats) { + print(stats); +} + +function getDeviceData(deviceType, deviceId){ + var device = deviceModule.getDevice(deviceType, deviceId); + if (!device){ + return; + } + switch (deviceType) { + case "firealarm": + getFireAlarmData(user.username, device, from, to); + break; + case "sensebot": + getSensebotData(user.username, device, from, to); + break; + case "arduino": + getArduinoData(user.username, device, from, to); + break; + case "digital_display": + getDigitalDisplayData(user.username, device, from, to); + break; + case "android_sense": + getAndroidSenseData(user.username, device, from, to); + break; + case "raspberrypi": + getDigitalDisplayData(user.username, device, from, to); + break; + } } -function getFireAlarmData(user, deviceId, from, to) { - result = new Object(); - result['cpuTemperatureData'] = getSensorData("DEVICE_CPU_TEMPERATURE_SUMMARY", "TEMPERATURE", user, deviceId, from, to); - result['temperatureData'] = getSensorData("DEVICE_TEMPERATURE_SUMMARY", "TEMPERATURE", user, deviceId, from, to); - result['fanData'] = getSensorData("DEVICE_FAN_USAGE_SUMMARY", "status", user, deviceId, from, to); - result['bulbData'] = getSensorData("DEVICE_BULB_USAGE_SUMMARY", "status", user, deviceId, from, to); - result['sonarData'] = getSensorData("SONAR_SENSOR_SUMMARY", "sonar", user, deviceId, from, to); - return result; +function getFireAlarmData(user, device, from, to) { + if (stats['cpuTemperatureData'] == null){ + stats['cpuTemperatureData'] = []; + } + if (stats['temperatureData'] == null){ + stats['temperatureData'] = []; + } + if (stats['fanData'] == null){ + stats['fanData'] = []; + } + if (stats['bulbData'] == null){ + stats['bulbData'] = []; + } + if (stats['sonarData'] == null){ + stats['sonarData'] = []; + } + stats['cpuTemperatureData'].push({"device": device.name, "stats" : getSensorData("DEVICE_CPU_TEMPERATURE_SUMMARY", "TEMPERATURE", user, device.deviceIdentifier, from, to)}); + stats['temperatureData'].push({"device": device.name, "stats" : getSensorData("DEVICE_TEMPERATURE_SUMMARY", "TEMPERATURE", user, device.deviceIdentifier, from, to)}); + stats['fanData'].push({"device": device.name, "stats" : getSensorData("DEVICE_FAN_USAGE_SUMMARY", "status", user, device.deviceIdentifier, from, to)}); + stats['bulbData'].push({"device": device.name, "stats" : getSensorData("DEVICE_BULB_USAGE_SUMMARY", "status", user, device.deviceIdentifier, from, to)}); + stats['sonarData'].push({"device": device.name, "stats" : getSensorData("SONAR_SENSOR_SUMMARY", "sonar", user, device.deviceIdentifier, from, to)}); } -function getSensebotData(user, deviceId, from, to) { - result = new Object(); - result['sonarData'] = getSensorData("SONAR_SENSOR_SUMMARY", "sonar", user, deviceId, from, to); - result['motionData'] = getSensorData("PIR_MOTION_SENSOR_SUMMARY", "motion", user, deviceId, from, to); - result['lightData'] = getSensorData("LDR_LIGHT_SENSOR_SUMMARY", "light", user, deviceId, from, to); - result['temperatureData'] = getSensorData("DEVICE_TEMPERATURE_SUMMARY", "TEMPERATURE", user, deviceId, from, to); - return result; +function getSensebotData(user, device, from, to) { + if (stats['sonarData'] == null){ + stats['sonarData'] = []; + } + if (stats['motionData'] == null){ + stats['motionData'] = []; + } + if (stats['lightData'] == null){ + stats['lightData'] = []; + } + if (stats['temperatureData'] == null){ + stats['temperatureData'] = []; + } + stats['sonarData'].push({"device": device.name, "stats" : getSensorData("SONAR_SENSOR_SUMMARY", "sonar", user, device.deviceIdentifier, from, to)}); + stats['motionData'].push({"device": device.name, "stats" : getSensorData("PIR_MOTION_SENSOR_SUMMARY", "motion", user, device.deviceIdentifier, from, to)}); + stats['lightData'].push({"device": device.name, "stats" : getSensorData("LDR_LIGHT_SENSOR_SUMMARY", "light", user, device.deviceIdentifier, from, to)}); + stats['temperatureData'].push({"device": device.name, "stats" : getSensorData("DEVICE_TEMPERATURE_SUMMARY", "TEMPERATURE", user, device.deviceIdentifier, from, to)}); } -function getArduinoData(user, deviceId, from, to) { - result = new Object(); - result['temperatureData'] = getSensorData("DEVICE_TEMPERATURE_SUMMARY", "TEMPERATURE", user, deviceId, from, to); - return result; +function getArduinoData(user, device, from, to) { + if (stats['temperatureData'] == null){ + stats['temperatureData'] = []; + } + stats['temperatureData'].push({"device": device.name, "stats" : getSensorData("DEVICE_TEMPERATURE_SUMMARY", "TEMPERATURE", user, device.deviceIdentifier, from, to)}); } -function getAndroidSenseData(user, deviceId, from, to) { - result = new Object(); - result['ramData'] = getSensorData("RAM_USAGE_SUMMARY", "motion", user, deviceId, from, to); - result['cpuData'] = getSensorData("CPU_LOAD_SUMMARY", "light", user, deviceId, from, to); - result['temperatureData'] = getSensorData("DEVICE_TEMPERATURE_SUMMARY", "TEMPERATURE", user, deviceId, from, to); - result['motionData'] = getSensorData("PIR_MOTION_SENSOR_SUMMARY", "motion", user, deviceId, from, to); - return result; +function getAndroidSenseData(user, device, from, to) { + if (stats['ramData'] == null){ + stats['ramData'] = []; + } + if (stats['cpuData'] == null){ + stats['cpuData'] = []; + } + if (stats['temperatureData'] == null){ + stats['temperatureData'] = []; + } + if (stats['motionData'] == null){ + stats['motionData'] = []; + } + stats['ramData'].push({"device": device.name, "stats" : getSensorData("RAM_USAGE_SUMMARY", "motion", user, device.deviceIdentifier, from, to)}); + stats['cpuData'].push({"device": device.name, "stats" : getSensorData("CPU_LOAD_SUMMARY", "light", user, device.deviceIdentifier, from, to)}); + stats['temperatureData'].push({"device": device.name, "stats" : getSensorData("DEVICE_TEMPERATURE_SUMMARY", "TEMPERATURE", user, device.deviceIdentifier, from, to)}); + stats['motionData'].push({"device": device.name, "stats" : getSensorData("PIR_MOTION_SENSOR_SUMMARY", "motion", user, device.deviceIdentifier, from, to)}); } -function getDigitalDisplayData(user, deviceId, from, to) { - result = new Object(); - result['ramData'] = getSensorData("RAM_USAGE_SUMMARY", "motion", user, deviceId, from, to); - result['cpuData'] = getSensorData("CPU_LOAD_SUMMARY", "light", user, deviceId, from, to); - result['cpuTemperatureData'] = getSensorData("DEVICE_CPU_TEMPERATURE_SUMMARY", "TEMPERATURE", user, deviceId, from, to); - return result; +function getDigitalDisplayData(user, device, from, to) { + if (stats['ramData'] == null){ + stats['ramData'] = []; + } + if (stats['cpuData'] == null){ + stats['cpuData'] = []; + } + if (stats['cpuTemperatureData'] == null){ + stats['cpuTemperatureData'] = []; + } + stats['ramData'].push({"device": device.name, "stats" : getSensorData("RAM_USAGE_SUMMARY", "motion", user, device.deviceIdentifier, from, to)}); + stats['cpuData'].push({"device": device.name, "stats" : getSensorData("CPU_LOAD_SUMMARY", "light", user, device.deviceIdentifier, from, to)}); + stats['cpuTemperatureData'].push({"device": device.name, "stats" : getSensorData("DEVICE_CPU_TEMPERATURE_SUMMARY", "TEMPERATURE", user, device.deviceIdentifier, from, to)}); } -function getSensorData(table, column, user, deviceId, from, to) { +function getSensorData(table, column, user, deviceIdentifier, from, to) { var fetchedData = null; try { - fetchedData = statsClient.getDeviceStats(table, column, user, deviceId, from, to); + fetchedData = statsClient.getDeviceStats(table, column, user, deviceIdentifier, from, to); } catch (error) { log.error(error); } diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/modules/device.js b/modules/distribution/src/repository/jaggeryapps/iotserver/modules/device.js index 845c2ea2..ae25e845 100644 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/modules/device.js +++ b/modules/distribution/src/repository/jaggeryapps/iotserver/modules/device.js @@ -232,7 +232,7 @@ deviceModule = function () { deviceManagementService.addOperation(operationInstance, deviceList); }; - privateMethods.getDevice = function (type, deviceId) { + publicMethods.getDevice = function (type, deviceId) { var deviceIdentifier = new DeviceIdentifier(); deviceIdentifier.setType(type); deviceIdentifier.setId(deviceId); @@ -240,7 +240,7 @@ deviceModule = function () { }; publicMethods.viewDevice = function (type, deviceId) { - var device = privateMethods.getDevice(type, deviceId); + var device = publicMethods.getDevice(type, deviceId); if (device) { var propertiesList = DeviceManagerUtil.convertDevicePropertiesToMap(device.getProperties()); var entries = propertiesList.entrySet(); diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/pages/groups/analytics.hbs b/modules/distribution/src/repository/jaggeryapps/iotserver/pages/analytics.hbs similarity index 65% rename from modules/distribution/src/repository/jaggeryapps/iotserver/pages/groups/analytics.hbs rename to modules/distribution/src/repository/jaggeryapps/iotserver/pages/analytics.hbs index d34ba39c..31264fd6 100644 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/pages/groups/analytics.hbs +++ b/modules/distribution/src/repository/jaggeryapps/iotserver/pages/analytics.hbs @@ -4,6 +4,6 @@ WSO2 DC | Analytics {{/zone}} {{#zone "body"}} - {{unit "appbar" link="analytics" title="Group Analytics"}} + {{unit "appbar" link="analytics" title="Analytics"}} {{unit "analytics"}} {{/zone}} \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/pages/devices/analytics.hbs b/modules/distribution/src/repository/jaggeryapps/iotserver/pages/devices/analytics.hbs deleted file mode 100644 index 5f42efc4..00000000 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/pages/devices/analytics.hbs +++ /dev/null @@ -1,9 +0,0 @@ -{{authorized}} -{{layout "fluid"}} -{{#zone "title"}} - WSO2 DC | FireAlarm -{{/zone}} -{{#zone "body"}} - {{unit "appbar" link="analytics" title="Device Analytics"}} - {{unit "analytics"}} -{{/zone}} \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/pages/groups/index.hbs b/modules/distribution/src/repository/jaggeryapps/iotserver/pages/groups.hbs similarity index 100% rename from modules/distribution/src/repository/jaggeryapps/iotserver/pages/groups/index.hbs rename to modules/distribution/src/repository/jaggeryapps/iotserver/pages/groups.hbs diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analiytics-graphs/analiytics-graphs.hbs b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analiytics-graphs/analiytics-graphs.hbs index bfbfcf19..5f9a0e57 100644 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analiytics-graphs/analiytics-graphs.hbs +++ b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analiytics-graphs/analiytics-graphs.hbs @@ -3,4 +3,6 @@ + + {{/zone}} \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analiytics-graphs/analiytics-graphs.json b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analiytics-graphs/analiytics-graphs.json index b199b1be..56db749f 100644 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analiytics-graphs/analiytics-graphs.json +++ b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analiytics-graphs/analiytics-graphs.json @@ -1,3 +1,3 @@ { - "predicate": "urlMatch('/devices/analytics')" + "predicate": "urlMatch('/analytics')" } \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analiytics-graphs/public/css/detail.css b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analiytics-graphs/public/css/detail.css new file mode 100644 index 00000000..501719f4 --- /dev/null +++ b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analiytics-graphs/public/css/detail.css @@ -0,0 +1,105 @@ +.rickshaw_graph .detail { + pointer-events: none; + position: absolute; + top: 0; + z-index: 2; + background: rgba(0, 0, 0, 0.1); + bottom: 0; + width: 1px; + transition: opacity 0.25s linear; + -moz-transition: opacity 0.25s linear; + -o-transition: opacity 0.25s linear; + -webkit-transition: opacity 0.25s linear; +} +.rickshaw_graph .detail.inactive { + opacity: 0; +} +.rickshaw_graph .detail .item.active { + opacity: 1; +} +.rickshaw_graph .detail .x_label { + font-family: Arial, sans-serif; + border-radius: 3px; + padding: 6px; + opacity: 0.5; + border: 1px solid #e0e0e0; + font-size: 12px; + position: absolute; + background: white; + white-space: nowrap; +} +.rickshaw_graph .detail .x_label.left { + left: 0; +} +.rickshaw_graph .detail .x_label.right { + right: 0; +} +.rickshaw_graph .detail .item { + position: absolute; + z-index: 2; + border-radius: 3px; + padding: 0.25em; + font-size: 12px; + font-family: Arial, sans-serif; + opacity: 0; + background: rgba(0, 0, 0, 0.4); + color: white; + border: 1px solid rgba(0, 0, 0, 0.4); + margin-left: 1em; + margin-right: 1em; + margin-top: -1em; + white-space: nowrap; +} +.rickshaw_graph .detail .item.left { + left: 0; +} +.rickshaw_graph .detail .item.right { + right: 0; +} +.rickshaw_graph .detail .item.active { + opacity: 1; + background: rgba(0, 0, 0, 0.8); +} +.rickshaw_graph .detail .item:after { + position: absolute; + display: block; + width: 0; + height: 0; + + content: ""; + + border: 5px solid transparent; +} +.rickshaw_graph .detail .item.left:after { + top: 1em; + left: -5px; + margin-top: -5px; + border-right-color: rgba(0, 0, 0, 0.8); + border-left-width: 0; +} +.rickshaw_graph .detail .item.right:after { + top: 1em; + right: -5px; + margin-top: -5px; + border-left-color: rgba(0, 0, 0, 0.8); + border-right-width: 0; +} +.rickshaw_graph .detail .dot { + width: 4px; + height: 4px; + margin-left: -3px; + margin-top: -3.5px; + border-radius: 5px; + position: absolute; + box-shadow: 0 0 2px rgba(0, 0, 0, 0.6); + box-sizing: content-box; + -moz-box-sizing: content-box; + background: white; + border-width: 2px; + border-style: solid; + display: none; + background-clip: padding-box; +} +.rickshaw_graph .detail .dot.active { + display: block; +} \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analiytics-graphs/public/css/graph.css b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analiytics-graphs/public/css/graph.css index ba03ec5c..0a79c8ae 100644 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analiytics-graphs/public/css/graph.css +++ b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analiytics-graphs/public/css/graph.css @@ -4,7 +4,7 @@ position: relative; } .rickshaw_graph svg { - display: block; + display: block; overflow: hidden; } @@ -151,7 +151,7 @@ .rickshaw_graph .y_ticks.glow text { fill: black; color: black; - text-shadow: + text-shadow: -1px 1px 0 rgba(255, 255, 255, 0.1), 1px -1px 0 rgba(255, 255, 255, 0.1), 1px 1px 0 rgba(255, 255, 255, 0.1), @@ -165,7 +165,7 @@ .rickshaw_graph .y_ticks.inverse text { fill: white; color: white; - text-shadow: + text-shadow: -1px 1px 0 rgba(0, 0, 0, 0.8), 1px -1px 0 rgba(0, 0, 0, 0.8), 1px 1px 0 rgba(0, 0, 0, 0.8), diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analiytics-graphs/public/css/legend.css b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analiytics-graphs/public/css/legend.css new file mode 100644 index 00000000..29c7358a --- /dev/null +++ b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analiytics-graphs/public/css/legend.css @@ -0,0 +1,73 @@ +.rickshaw_legend { + font-family: Arial; + font-size: 12px; + color: white; + background: #404040; + display: inline-block; + padding: 12px 5px; + border-radius: 2px; + position: relative; +} +.rickshaw_legend:hover { + z-index: 10; +} +.rickshaw_legend .swatch { + width: 10px; + height: 10px; + border: 1px solid rgba(0, 0, 0, 0.2); +} +.rickshaw_legend .line { + clear: both; + line-height: 140%; + padding-right: 15px; +} +.rickshaw_legend .line .swatch { + display: inline-block; + margin-right: 3px; + border-radius: 2px; +} +.rickshaw_legend .label { + margin: 0; + white-space: nowrap; + display: inline; + font-size: inherit; + background-color: transparent; + color: inherit; + font-weight: normal; + line-height: normal; + padding: 0px; + text-shadow: none; +} +.rickshaw_legend .action:hover { + opacity: 0.6; +} +.rickshaw_legend .action { + margin-right: 0.2em; + font-size: 10px; + opacity: 0.2; + cursor: pointer; + font-size: 14px; +} +.rickshaw_legend .line.disabled { + opacity: 0.4; +} +.rickshaw_legend ul { + list-style-type: none; + margin: 0; + padding: 0; + margin: 2px; + cursor: pointer; +} +.rickshaw_legend li { + padding: 0 0 0 2px; + min-width: 80px; + white-space: nowrap; +} +.rickshaw_legend li:hover { + background: rgba(255, 255, 255, 0.08); + border-radius: 3px; +} +.rickshaw_legend li:active { + background: rgba(255, 255, 255, 0.2); + border-radius: 3px; +} \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analiytics-graphs/public/css/lines.css b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analiytics-graphs/public/css/lines.css index 2e724fac..b96fceb8 100644 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analiytics-graphs/public/css/lines.css +++ b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analiytics-graphs/public/css/lines.css @@ -1,3 +1,6 @@ +div, span, p, td { + font-family: Arial, sans-serif; +} #chart { display: inline-block; } @@ -15,4 +18,4 @@ #chart_container { float: left; position: relative; -} +} \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/analytics.hbs b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/analytics.hbs index 5fa265cb..10fc0fc2 100644 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/analytics.hbs +++ b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/analytics.hbs @@ -1,5 +1,5 @@ {{#zone "main"}} - +
@@ -64,14 +64,4 @@ - - - - - - - - - - {{/zone}} \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/Chart.min.js b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/Chart.min.js deleted file mode 100644 index 3a0a2c87..00000000 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/Chart.min.js +++ /dev/null @@ -1,11 +0,0 @@ -/*! - * Chart.js - * http://chartjs.org/ - * Version: 1.0.2 - * - * Copyright 2015 Nick Downie - * Released under the MIT license - * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md - */ -(function(){"use strict";var t=this,i=t.Chart,e=function(t){this.canvas=t.canvas,this.ctx=t;var i=function(t,i){return t["offset"+i]?t["offset"+i]:document.defaultView.getComputedStyle(t).getPropertyValue(i)},e=this.width=i(t.canvas,"Width"),n=this.height=i(t.canvas,"Height");t.canvas.width=e,t.canvas.height=n;var e=this.width=t.canvas.width,n=this.height=t.canvas.height;return this.aspectRatio=this.width/this.height,s.retinaScale(this),this};e.defaults={global:{animation:!0,animationSteps:60,animationEasing:"easeOutQuart",showScale:!0,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleIntegersOnly:!0,scaleBeginAtZero:!1,scaleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",responsive:!1,maintainAspectRatio:!0,showTooltips:!0,customTooltips:!1,tooltipEvents:["mousemove","touchstart","touchmove","mouseout"],tooltipFillColor:"rgba(0,0,0,0.8)",tooltipFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",tooltipFontSize:14,tooltipFontStyle:"normal",tooltipFontColor:"#fff",tooltipTitleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",tooltipTitleFontSize:14,tooltipTitleFontStyle:"bold",tooltipTitleFontColor:"#fff",tooltipYPadding:6,tooltipXPadding:6,tooltipCaretSize:8,tooltipCornerRadius:6,tooltipXOffset:10,tooltipTemplate:"<%if (label){%><%=label%>: <%}%><%= value %>",multiTooltipTemplate:"<%= value %>",multiTooltipKeyBackground:"#fff",onAnimationProgress:function(){},onAnimationComplete:function(){}}},e.types={};var s=e.helpers={},n=s.each=function(t,i,e){var s=Array.prototype.slice.call(arguments,3);if(t)if(t.length===+t.length){var n;for(n=0;n=0;s--){var n=t[s];if(i(n))return n}},s.inherits=function(t){var i=this,e=t&&t.hasOwnProperty("constructor")?t.constructor:function(){return i.apply(this,arguments)},s=function(){this.constructor=e};return s.prototype=i.prototype,e.prototype=new s,e.extend=r,t&&a(e.prototype,t),e.__super__=i.prototype,e}),c=s.noop=function(){},u=s.uid=function(){var t=0;return function(){return"chart-"+t++}}(),d=s.warn=function(t){window.console&&"function"==typeof window.console.warn&&console.warn(t)},p=s.amd="function"==typeof define&&define.amd,f=s.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},g=s.max=function(t){return Math.max.apply(Math,t)},m=s.min=function(t){return Math.min.apply(Math,t)},v=(s.cap=function(t,i,e){if(f(i)){if(t>i)return i}else if(f(e)&&e>t)return e;return t},s.getDecimalPlaces=function(t){return t%1!==0&&f(t)?t.toString().split(".")[1].length:0}),S=s.radians=function(t){return t*(Math.PI/180)},x=(s.getAngleFromPoint=function(t,i){var e=i.x-t.x,s=i.y-t.y,n=Math.sqrt(e*e+s*s),o=2*Math.PI+Math.atan2(s,e);return 0>e&&0>s&&(o+=2*Math.PI),{angle:o,distance:n}},s.aliasPixel=function(t){return t%2===0?0:.5}),y=(s.splineCurve=function(t,i,e,s){var n=Math.sqrt(Math.pow(i.x-t.x,2)+Math.pow(i.y-t.y,2)),o=Math.sqrt(Math.pow(e.x-i.x,2)+Math.pow(e.y-i.y,2)),a=s*n/(n+o),h=s*o/(n+o);return{inner:{x:i.x-a*(e.x-t.x),y:i.y-a*(e.y-t.y)},outer:{x:i.x+h*(e.x-t.x),y:i.y+h*(e.y-t.y)}}},s.calculateOrderOfMagnitude=function(t){return Math.floor(Math.log(t)/Math.LN10)}),C=(s.calculateScaleRange=function(t,i,e,s,n){var o=2,a=Math.floor(i/(1.5*e)),h=o>=a,l=g(t),r=m(t);l===r&&(l+=.5,r>=.5&&!s?r-=.5:l+=.5);for(var c=Math.abs(l-r),u=y(c),d=Math.ceil(l/(1*Math.pow(10,u)))*Math.pow(10,u),p=s?0:Math.floor(r/(1*Math.pow(10,u)))*Math.pow(10,u),f=d-p,v=Math.pow(10,u),S=Math.round(f/v);(S>a||a>2*S)&&!h;)if(S>a)v*=2,S=Math.round(f/v),S%1!==0&&(h=!0);else if(n&&u>=0){if(v/2%1!==0)break;v/=2,S=Math.round(f/v)}else v/=2,S=Math.round(f/v);return h&&(S=o,v=f/S),{steps:S,stepValue:v,min:p,max:p+S*v}},s.template=function(t,i){function e(t,i){var e=/\W/.test(t)?new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+t.replace(/[\r\t\n]/g," ").split("<%").join(" ").replace(/((^|%>)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split(" ").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');"):s[t]=s[t];return i?e(i):e}if(t instanceof Function)return t(i);var s={};return e(t,i)}),w=(s.generateLabels=function(t,i,e,s){var o=new Array(i);return labelTemplateString&&n(o,function(i,n){o[n]=C(t,{value:e+s*(n+1)})}),o},s.easingEffects={linear:function(t){return t},easeInQuad:function(t){return t*t},easeOutQuad:function(t){return-1*t*(t-2)},easeInOutQuad:function(t){return(t/=.5)<1?.5*t*t:-0.5*(--t*(t-2)-1)},easeInCubic:function(t){return t*t*t},easeOutCubic:function(t){return 1*((t=t/1-1)*t*t+1)},easeInOutCubic:function(t){return(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2)},easeInQuart:function(t){return t*t*t*t},easeOutQuart:function(t){return-1*((t=t/1-1)*t*t*t-1)},easeInOutQuart:function(t){return(t/=.5)<1?.5*t*t*t*t:-0.5*((t-=2)*t*t*t-2)},easeInQuint:function(t){return 1*(t/=1)*t*t*t*t},easeOutQuint:function(t){return 1*((t=t/1-1)*t*t*t*t+1)},easeInOutQuint:function(t){return(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)},easeInSine:function(t){return-1*Math.cos(t/1*(Math.PI/2))+1},easeOutSine:function(t){return 1*Math.sin(t/1*(Math.PI/2))},easeInOutSine:function(t){return-0.5*(Math.cos(Math.PI*t/1)-1)},easeInExpo:function(t){return 0===t?1:1*Math.pow(2,10*(t/1-1))},easeOutExpo:function(t){return 1===t?1:1*(-Math.pow(2,-10*t/1)+1)},easeInOutExpo:function(t){return 0===t?0:1===t?1:(t/=.5)<1?.5*Math.pow(2,10*(t-1)):.5*(-Math.pow(2,-10*--t)+2)},easeInCirc:function(t){return t>=1?t:-1*(Math.sqrt(1-(t/=1)*t)-1)},easeOutCirc:function(t){return 1*Math.sqrt(1-(t=t/1-1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-0.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var i=1.70158,e=0,s=1;return 0===t?0:1==(t/=1)?1:(e||(e=.3),st?-.5*s*Math.pow(2,10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e):s*Math.pow(2,-10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e)*.5+1)},easeInBack:function(t){var i=1.70158;return 1*(t/=1)*t*((i+1)*t-i)},easeOutBack:function(t){var i=1.70158;return 1*((t=t/1-1)*t*((i+1)*t+i)+1)},easeInOutBack:function(t){var i=1.70158;return(t/=.5)<1?.5*t*t*(((i*=1.525)+1)*t-i):.5*((t-=2)*t*(((i*=1.525)+1)*t+i)+2)},easeInBounce:function(t){return 1-w.easeOutBounce(1-t)},easeOutBounce:function(t){return(t/=1)<1/2.75?7.5625*t*t:2/2.75>t?1*(7.5625*(t-=1.5/2.75)*t+.75):2.5/2.75>t?1*(7.5625*(t-=2.25/2.75)*t+.9375):1*(7.5625*(t-=2.625/2.75)*t+.984375)},easeInOutBounce:function(t){return.5>t?.5*w.easeInBounce(2*t):.5*w.easeOutBounce(2*t-1)+.5}}),b=s.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)}}(),P=s.cancelAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return window.clearTimeout(t,1e3/60)}}(),L=(s.animationLoop=function(t,i,e,s,n,o){var a=0,h=w[e]||w.linear,l=function(){a++;var e=a/i,r=h(e);t.call(o,r,e,a),s.call(o,r,e),i>a?o.animationFrame=b(l):n.apply(o)};b(l)},s.getRelativePosition=function(t){var i,e,s=t.originalEvent||t,n=t.currentTarget||t.srcElement,o=n.getBoundingClientRect();return s.touches?(i=s.touches[0].clientX-o.left,e=s.touches[0].clientY-o.top):(i=s.clientX-o.left,e=s.clientY-o.top),{x:i,y:e}},s.addEvent=function(t,i,e){t.addEventListener?t.addEventListener(i,e):t.attachEvent?t.attachEvent("on"+i,e):t["on"+i]=e}),k=s.removeEvent=function(t,i,e){t.removeEventListener?t.removeEventListener(i,e,!1):t.detachEvent?t.detachEvent("on"+i,e):t["on"+i]=c},F=(s.bindEvents=function(t,i,e){t.events||(t.events={}),n(i,function(i){t.events[i]=function(){e.apply(t,arguments)},L(t.chart.canvas,i,t.events[i])})},s.unbindEvents=function(t,i){n(i,function(i,e){k(t.chart.canvas,e,i)})}),R=s.getMaximumWidth=function(t){var i=t.parentNode;return i.clientWidth},T=s.getMaximumHeight=function(t){var i=t.parentNode;return i.clientHeight},A=(s.getMaximumSize=s.getMaximumWidth,s.retinaScale=function(t){var i=t.ctx,e=t.canvas.width,s=t.canvas.height;window.devicePixelRatio&&(i.canvas.style.width=e+"px",i.canvas.style.height=s+"px",i.canvas.height=s*window.devicePixelRatio,i.canvas.width=e*window.devicePixelRatio,i.scale(window.devicePixelRatio,window.devicePixelRatio))}),M=s.clear=function(t){t.ctx.clearRect(0,0,t.width,t.height)},W=s.fontString=function(t,i,e){return i+" "+t+"px "+e},z=s.longestText=function(t,i,e){t.font=i;var s=0;return n(e,function(i){var e=t.measureText(i).width;s=e>s?e:s}),s},B=s.drawRoundedRectangle=function(t,i,e,s,n,o){t.beginPath(),t.moveTo(i+o,e),t.lineTo(i+s-o,e),t.quadraticCurveTo(i+s,e,i+s,e+o),t.lineTo(i+s,e+n-o),t.quadraticCurveTo(i+s,e+n,i+s-o,e+n),t.lineTo(i+o,e+n),t.quadraticCurveTo(i,e+n,i,e+n-o),t.lineTo(i,e+o),t.quadraticCurveTo(i,e,i+o,e),t.closePath()};e.instances={},e.Type=function(t,i,s){this.options=i,this.chart=s,this.id=u(),e.instances[this.id]=this,i.responsive&&this.resize(),this.initialize.call(this,t)},a(e.Type.prototype,{initialize:function(){return this},clear:function(){return M(this.chart),this},stop:function(){return P(this.animationFrame),this},resize:function(t){this.stop();var i=this.chart.canvas,e=R(this.chart.canvas),s=this.options.maintainAspectRatio?e/this.chart.aspectRatio:T(this.chart.canvas);return i.width=this.chart.width=e,i.height=this.chart.height=s,A(this.chart),"function"==typeof t&&t.apply(this,Array.prototype.slice.call(arguments,1)),this},reflow:c,render:function(t){return t&&this.reflow(),this.options.animation&&!t?s.animationLoop(this.draw,this.options.animationSteps,this.options.animationEasing,this.options.onAnimationProgress,this.options.onAnimationComplete,this):(this.draw(),this.options.onAnimationComplete.call(this)),this},generateLegend:function(){return C(this.options.legendTemplate,this)},destroy:function(){this.clear(),F(this,this.events);var t=this.chart.canvas;t.width=this.chart.width,t.height=this.chart.height,t.style.removeProperty?(t.style.removeProperty("width"),t.style.removeProperty("height")):(t.style.removeAttribute("width"),t.style.removeAttribute("height")),delete e.instances[this.id]},showTooltip:function(t,i){"undefined"==typeof this.activeElements&&(this.activeElements=[]);var o=function(t){var i=!1;return t.length!==this.activeElements.length?i=!0:(n(t,function(t,e){t!==this.activeElements[e]&&(i=!0)},this),i)}.call(this,t);if(o||i){if(this.activeElements=t,this.draw(),this.options.customTooltips&&this.options.customTooltips(!1),t.length>0)if(this.datasets&&this.datasets.length>1){for(var a,h,r=this.datasets.length-1;r>=0&&(a=this.datasets[r].points||this.datasets[r].bars||this.datasets[r].segments,h=l(a,t[0]),-1===h);r--);var c=[],u=[],d=function(){var t,i,e,n,o,a=[],l=[],r=[];return s.each(this.datasets,function(i){t=i.points||i.bars||i.segments,t[h]&&t[h].hasValue()&&a.push(t[h])}),s.each(a,function(t){l.push(t.x),r.push(t.y),c.push(s.template(this.options.multiTooltipTemplate,t)),u.push({fill:t._saved.fillColor||t.fillColor,stroke:t._saved.strokeColor||t.strokeColor})},this),o=m(r),e=g(r),n=m(l),i=g(l),{x:n>this.chart.width/2?n:i,y:(o+e)/2}}.call(this,h);new e.MultiTooltip({x:d.x,y:d.y,xPadding:this.options.tooltipXPadding,yPadding:this.options.tooltipYPadding,xOffset:this.options.tooltipXOffset,fillColor:this.options.tooltipFillColor,textColor:this.options.tooltipFontColor,fontFamily:this.options.tooltipFontFamily,fontStyle:this.options.tooltipFontStyle,fontSize:this.options.tooltipFontSize,titleTextColor:this.options.tooltipTitleFontColor,titleFontFamily:this.options.tooltipTitleFontFamily,titleFontStyle:this.options.tooltipTitleFontStyle,titleFontSize:this.options.tooltipTitleFontSize,cornerRadius:this.options.tooltipCornerRadius,labels:c,legendColors:u,legendColorBackground:this.options.multiTooltipKeyBackground,title:t[0].label,chart:this.chart,ctx:this.chart.ctx,custom:this.options.customTooltips}).draw()}else n(t,function(t){var i=t.tooltipPosition();new e.Tooltip({x:Math.round(i.x),y:Math.round(i.y),xPadding:this.options.tooltipXPadding,yPadding:this.options.tooltipYPadding,fillColor:this.options.tooltipFillColor,textColor:this.options.tooltipFontColor,fontFamily:this.options.tooltipFontFamily,fontStyle:this.options.tooltipFontStyle,fontSize:this.options.tooltipFontSize,caretHeight:this.options.tooltipCaretSize,cornerRadius:this.options.tooltipCornerRadius,text:C(this.options.tooltipTemplate,t),chart:this.chart,custom:this.options.customTooltips}).draw()},this);return this}},toBase64Image:function(){return this.chart.canvas.toDataURL.apply(this.chart.canvas,arguments)}}),e.Type.extend=function(t){var i=this,s=function(){return i.apply(this,arguments)};if(s.prototype=o(i.prototype),a(s.prototype,t),s.extend=e.Type.extend,t.name||i.prototype.name){var n=t.name||i.prototype.name,l=e.defaults[i.prototype.name]?o(e.defaults[i.prototype.name]):{};e.defaults[n]=a(l,t.defaults),e.types[n]=s,e.prototype[n]=function(t,i){var o=h(e.defaults.global,e.defaults[n],i||{});return new s(t,o,this)}}else d("Name not provided for this chart, so it hasn't been registered");return i},e.Element=function(t){a(this,t),this.initialize.apply(this,arguments),this.save()},a(e.Element.prototype,{initialize:function(){},restore:function(t){return t?n(t,function(t){this[t]=this._saved[t]},this):a(this,this._saved),this},save:function(){return this._saved=o(this),delete this._saved._saved,this},update:function(t){return n(t,function(t,i){this._saved[i]=this[i],this[i]=t},this),this},transition:function(t,i){return n(t,function(t,e){this[e]=(t-this._saved[e])*i+this._saved[e]},this),this},tooltipPosition:function(){return{x:this.x,y:this.y}},hasValue:function(){return f(this.value)}}),e.Element.extend=r,e.Point=e.Element.extend({display:!0,inRange:function(t,i){var e=this.hitDetectionRadius+this.radius;return Math.pow(t-this.x,2)+Math.pow(i-this.y,2)=this.startAngle&&e.angle<=this.endAngle,o=e.distance>=this.innerRadius&&e.distance<=this.outerRadius;return n&&o},tooltipPosition:function(){var t=this.startAngle+(this.endAngle-this.startAngle)/2,i=(this.outerRadius-this.innerRadius)/2+this.innerRadius;return{x:this.x+Math.cos(t)*i,y:this.y+Math.sin(t)*i}},draw:function(t){var i=this.ctx;i.beginPath(),i.arc(this.x,this.y,this.outerRadius,this.startAngle,this.endAngle),i.arc(this.x,this.y,this.innerRadius,this.endAngle,this.startAngle,!0),i.closePath(),i.strokeStyle=this.strokeColor,i.lineWidth=this.strokeWidth,i.fillStyle=this.fillColor,i.fill(),i.lineJoin="bevel",this.showStroke&&i.stroke()}}),e.Rectangle=e.Element.extend({draw:function(){var t=this.ctx,i=this.width/2,e=this.x-i,s=this.x+i,n=this.base-(this.base-this.y),o=this.strokeWidth/2;this.showStroke&&(e+=o,s-=o,n+=o),t.beginPath(),t.fillStyle=this.fillColor,t.strokeStyle=this.strokeColor,t.lineWidth=this.strokeWidth,t.moveTo(e,this.base),t.lineTo(e,n),t.lineTo(s,n),t.lineTo(s,this.base),t.fill(),this.showStroke&&t.stroke()},height:function(){return this.base-this.y},inRange:function(t,i){return t>=this.x-this.width/2&&t<=this.x+this.width/2&&i>=this.y&&i<=this.base}}),e.Tooltip=e.Element.extend({draw:function(){var t=this.chart.ctx;t.font=W(this.fontSize,this.fontStyle,this.fontFamily),this.xAlign="center",this.yAlign="above";var i=this.caretPadding=2,e=t.measureText(this.text).width+2*this.xPadding,s=this.fontSize+2*this.yPadding,n=s+this.caretHeight+i;this.x+e/2>this.chart.width?this.xAlign="left":this.x-e/2<0&&(this.xAlign="right"),this.y-n<0&&(this.yAlign="below");var o=this.x-e/2,a=this.y-n;if(t.fillStyle=this.fillColor,this.custom)this.custom(this);else{switch(this.yAlign){case"above":t.beginPath(),t.moveTo(this.x,this.y-i),t.lineTo(this.x+this.caretHeight,this.y-(i+this.caretHeight)),t.lineTo(this.x-this.caretHeight,this.y-(i+this.caretHeight)),t.closePath(),t.fill();break;case"below":a=this.y+i+this.caretHeight,t.beginPath(),t.moveTo(this.x,this.y+i),t.lineTo(this.x+this.caretHeight,this.y+i+this.caretHeight),t.lineTo(this.x-this.caretHeight,this.y+i+this.caretHeight),t.closePath(),t.fill()}switch(this.xAlign){case"left":o=this.x-e+(this.cornerRadius+this.caretHeight);break;case"right":o=this.x-(this.cornerRadius+this.caretHeight)}B(t,o,a,e,s,this.cornerRadius),t.fill(),t.fillStyle=this.textColor,t.textAlign="center",t.textBaseline="middle",t.fillText(this.text,o+e/2,a+s/2)}}}),e.MultiTooltip=e.Element.extend({initialize:function(){this.font=W(this.fontSize,this.fontStyle,this.fontFamily),this.titleFont=W(this.titleFontSize,this.titleFontStyle,this.titleFontFamily),this.height=this.labels.length*this.fontSize+(this.labels.length-1)*(this.fontSize/2)+2*this.yPadding+1.5*this.titleFontSize,this.ctx.font=this.titleFont;var t=this.ctx.measureText(this.title).width,i=z(this.ctx,this.font,this.labels)+this.fontSize+3,e=g([i,t]);this.width=e+2*this.xPadding;var s=this.height/2;this.y-s<0?this.y=s:this.y+s>this.chart.height&&(this.y=this.chart.height-s),this.x>this.chart.width/2?this.x-=this.xOffset+this.width:this.x+=this.xOffset},getLineHeight:function(t){var i=this.y-this.height/2+this.yPadding,e=t-1;return 0===t?i+this.titleFontSize/2:i+(1.5*this.fontSize*e+this.fontSize/2)+1.5*this.titleFontSize},draw:function(){if(this.custom)this.custom(this);else{B(this.ctx,this.x,this.y-this.height/2,this.width,this.height,this.cornerRadius);var t=this.ctx;t.fillStyle=this.fillColor,t.fill(),t.closePath(),t.textAlign="left",t.textBaseline="middle",t.fillStyle=this.titleTextColor,t.font=this.titleFont,t.fillText(this.title,this.x+this.xPadding,this.getLineHeight(0)),t.font=this.font,s.each(this.labels,function(i,e){t.fillStyle=this.textColor,t.fillText(i,this.x+this.xPadding+this.fontSize+3,this.getLineHeight(e+1)),t.fillStyle=this.legendColorBackground,t.fillRect(this.x+this.xPadding,this.getLineHeight(e+1)-this.fontSize/2,this.fontSize,this.fontSize),t.fillStyle=this.legendColors[e].fill,t.fillRect(this.x+this.xPadding,this.getLineHeight(e+1)-this.fontSize/2,this.fontSize,this.fontSize)},this)}}}),e.Scale=e.Element.extend({initialize:function(){this.fit()},buildYLabels:function(){this.yLabels=[];for(var t=v(this.stepValue),i=0;i<=this.steps;i++)this.yLabels.push(C(this.templateString,{value:(this.min+i*this.stepValue).toFixed(t)}));this.yLabelWidth=this.display&&this.showLabels?z(this.ctx,this.font,this.yLabels):0},addXLabel:function(t){this.xLabels.push(t),this.valuesCount++,this.fit()},removeXLabel:function(){this.xLabels.shift(),this.valuesCount--,this.fit()},fit:function(){this.startPoint=this.display?this.fontSize:0,this.endPoint=this.display?this.height-1.5*this.fontSize-5:this.height,this.startPoint+=this.padding,this.endPoint-=this.padding;var t,i=this.endPoint-this.startPoint;for(this.calculateYRange(i),this.buildYLabels(),this.calculateXLabelRotation();i>this.endPoint-this.startPoint;)i=this.endPoint-this.startPoint,t=this.yLabelWidth,this.calculateYRange(i),this.buildYLabels(),tthis.yLabelWidth+10?e/2:this.yLabelWidth+10,this.xLabelRotation=0,this.display){var n,o=z(this.ctx,this.font,this.xLabels);this.xLabelWidth=o;for(var a=Math.floor(this.calculateX(1)-this.calculateX(0))-6;this.xLabelWidth>a&&0===this.xLabelRotation||this.xLabelWidth>a&&this.xLabelRotation<=90&&this.xLabelRotation>0;)n=Math.cos(S(this.xLabelRotation)),t=n*e,i=n*s,t+this.fontSize/2>this.yLabelWidth+8&&(this.xScalePaddingLeft=t+this.fontSize/2),this.xScalePaddingRight=this.fontSize/2,this.xLabelRotation++,this.xLabelWidth=n*o;this.xLabelRotation>0&&(this.endPoint-=Math.sin(S(this.xLabelRotation))*o+3)}else this.xLabelWidth=0,this.xScalePaddingRight=this.padding,this.xScalePaddingLeft=this.padding},calculateYRange:c,drawingArea:function(){return this.startPoint-this.endPoint},calculateY:function(t){var i=this.drawingArea()/(this.min-this.max);return this.endPoint-i*(t-this.min)},calculateX:function(t){var i=(this.xLabelRotation>0,this.width-(this.xScalePaddingLeft+this.xScalePaddingRight)),e=i/Math.max(this.valuesCount-(this.offsetGridLines?0:1),1),s=e*t+this.xScalePaddingLeft;return this.offsetGridLines&&(s+=e/2),Math.round(s)},update:function(t){s.extend(this,t),this.fit()},draw:function(){var t=this.ctx,i=(this.endPoint-this.startPoint)/this.steps,e=Math.round(this.xScalePaddingLeft);this.display&&(t.fillStyle=this.textColor,t.font=this.font,n(this.yLabels,function(n,o){var a=this.endPoint-i*o,h=Math.round(a),l=this.showHorizontalLines;t.textAlign="right",t.textBaseline="middle",this.showLabels&&t.fillText(n,e-10,a),0!==o||l||(l=!0),l&&t.beginPath(),o>0?(t.lineWidth=this.gridLineWidth,t.strokeStyle=this.gridLineColor):(t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor),h+=s.aliasPixel(t.lineWidth),l&&(t.moveTo(e,h),t.lineTo(this.width,h),t.stroke(),t.closePath()),t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor,t.beginPath(),t.moveTo(e-5,h),t.lineTo(e,h),t.stroke(),t.closePath()},this),n(this.xLabels,function(i,e){var s=this.calculateX(e)+x(this.lineWidth),n=this.calculateX(e-(this.offsetGridLines?.5:0))+x(this.lineWidth),o=this.xLabelRotation>0,a=this.showVerticalLines;0!==e||a||(a=!0),a&&t.beginPath(),e>0?(t.lineWidth=this.gridLineWidth,t.strokeStyle=this.gridLineColor):(t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor),a&&(t.moveTo(n,this.endPoint),t.lineTo(n,this.startPoint-3),t.stroke(),t.closePath()),t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor,t.beginPath(),t.moveTo(n,this.endPoint),t.lineTo(n,this.endPoint+5),t.stroke(),t.closePath(),t.save(),t.translate(s,o?this.endPoint+12:this.endPoint+8),t.rotate(-1*S(this.xLabelRotation)),t.font=this.font,t.textAlign=o?"right":"center",t.textBaseline=o?"middle":"top",t.fillText(i,0,0),t.restore()},this))}}),e.RadialScale=e.Element.extend({initialize:function(){this.size=m([this.height,this.width]),this.drawingArea=this.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2},calculateCenterOffset:function(t){var i=this.drawingArea/(this.max-this.min);return(t-this.min)*i},update:function(){this.lineArc?this.drawingArea=this.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2:this.setScaleSize(),this.buildYLabels()},buildYLabels:function(){this.yLabels=[];for(var t=v(this.stepValue),i=0;i<=this.steps;i++)this.yLabels.push(C(this.templateString,{value:(this.min+i*this.stepValue).toFixed(t)}))},getCircumference:function(){return 2*Math.PI/this.valuesCount},setScaleSize:function(){var t,i,e,s,n,o,a,h,l,r,c,u,d=m([this.height/2-this.pointLabelFontSize-5,this.width/2]),p=this.width,g=0;for(this.ctx.font=W(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily),i=0;ip&&(p=t.x+s,n=i),t.x-sp&&(p=t.x+e,n=i):i>this.valuesCount/2&&t.x-e0){var s,n=e*(this.drawingArea/this.steps),o=this.yCenter-n;if(this.lineWidth>0)if(t.strokeStyle=this.lineColor,t.lineWidth=this.lineWidth,this.lineArc)t.beginPath(),t.arc(this.xCenter,this.yCenter,n,0,2*Math.PI),t.closePath(),t.stroke();else{t.beginPath();for(var a=0;a=0;i--){if(this.angleLineWidth>0){var e=this.getPointPosition(i,this.calculateCenterOffset(this.max));t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(e.x,e.y),t.stroke(),t.closePath()}var s=this.getPointPosition(i,this.calculateCenterOffset(this.max)+5);t.font=W(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily),t.fillStyle=this.pointLabelFontColor;var o=this.labels.length,a=this.labels.length/2,h=a/2,l=h>i||i>o-h,r=i===h||i===o-h;t.textAlign=0===i?"center":i===a?"center":a>i?"left":"right",t.textBaseline=r?"middle":l?"bottom":"top",t.fillText(this.labels[i],s.x,s.y)}}}}}),s.addEvent(window,"resize",function(){var t;return function(){clearTimeout(t),t=setTimeout(function(){n(e.instances,function(t){t.options.responsive&&t.resize(t.render,!0)})},50)}}()),p?define(function(){return e}):"object"==typeof module&&module.exports&&(module.exports=e),t.Chart=e,e.noConflict=function(){return t.Chart=i,e}}).call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={scaleBeginAtZero:!0,scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,scaleShowHorizontalLines:!0,scaleShowVerticalLines:!0,barShowStroke:!0,barStrokeWidth:2,barValueSpacing:5,barDatasetSpacing:1,legendTemplate:'
    <% for (var i=0; i
  • <%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>
'};i.Type.extend({name:"Bar",defaults:s,initialize:function(t){var s=this.options;this.ScaleClass=i.Scale.extend({offsetGridLines:!0,calculateBarX:function(t,i,e){var n=this.calculateBaseWidth(),o=this.calculateX(e)-n/2,a=this.calculateBarWidth(t);return o+a*i+i*s.barDatasetSpacing+a/2},calculateBaseWidth:function(){return this.calculateX(1)-this.calculateX(0)-2*s.barValueSpacing},calculateBarWidth:function(t){var i=this.calculateBaseWidth()-(t-1)*s.barDatasetSpacing;return i/t}}),this.datasets=[],this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getBarsAtEvent(t):[];this.eachBars(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),this.BarClass=i.Rectangle.extend({strokeWidth:this.options.barStrokeWidth,showStroke:this.options.barShowStroke,ctx:this.chart.ctx}),e.each(t.datasets,function(i){var s={label:i.label||null,fillColor:i.fillColor,strokeColor:i.strokeColor,bars:[]};this.datasets.push(s),e.each(i.data,function(e,n){s.bars.push(new this.BarClass({value:e,label:t.labels[n],datasetLabel:i.label,strokeColor:i.strokeColor,fillColor:i.fillColor,highlightFill:i.highlightFill||i.fillColor,highlightStroke:i.highlightStroke||i.strokeColor}))},this)},this),this.buildScale(t.labels),this.BarClass.prototype.base=this.scale.endPoint,this.eachBars(function(t,i,s){e.extend(t,{width:this.scale.calculateBarWidth(this.datasets.length),x:this.scale.calculateBarX(this.datasets.length,s,i),y:this.scale.endPoint}),t.save()},this),this.render()},update:function(){this.scale.update(),e.each(this.activeElements,function(t){t.restore(["fillColor","strokeColor"])}),this.eachBars(function(t){t.save()}),this.render()},eachBars:function(t){e.each(this.datasets,function(i,s){e.each(i.bars,t,this,s)},this)},getBarsAtEvent:function(t){for(var i,s=[],n=e.getRelativePosition(t),o=function(t){s.push(t.bars[i])},a=0;a<% for (var i=0; i
  • <%if(segments[i].label){%><%=segments[i].label%><%}%>
  • <%}%>'};i.Type.extend({name:"Doughnut",defaults:s,initialize:function(t){this.segments=[],this.outerRadius=(e.min([this.chart.width,this.chart.height])-this.options.segmentStrokeWidth/2)/2,this.SegmentArc=i.Arc.extend({ctx:this.chart.ctx,x:this.chart.width/2,y:this.chart.height/2}),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getSegmentsAtEvent(t):[];e.each(this.segments,function(t){t.restore(["fillColor"])}),e.each(i,function(t){t.fillColor=t.highlightColor}),this.showTooltip(i)}),this.calculateTotal(t),e.each(t,function(t,i){this.addData(t,i,!0)},this),this.render()},getSegmentsAtEvent:function(t){var i=[],s=e.getRelativePosition(t);return e.each(this.segments,function(t){t.inRange(s.x,s.y)&&i.push(t)},this),i},addData:function(t,i,e){var s=i||this.segments.length;this.segments.splice(s,0,new this.SegmentArc({value:t.value,outerRadius:this.options.animateScale?0:this.outerRadius,innerRadius:this.options.animateScale?0:this.outerRadius/100*this.options.percentageInnerCutout,fillColor:t.color,highlightColor:t.highlight||t.color,showStroke:this.options.segmentShowStroke,strokeWidth:this.options.segmentStrokeWidth,strokeColor:this.options.segmentStrokeColor,startAngle:1.5*Math.PI,circumference:this.options.animateRotate?0:this.calculateCircumference(t.value),label:t.label})),e||(this.reflow(),this.update())},calculateCircumference:function(t){return 2*Math.PI*(Math.abs(t)/this.total)},calculateTotal:function(t){this.total=0,e.each(t,function(t){this.total+=Math.abs(t.value)},this)},update:function(){this.calculateTotal(this.segments),e.each(this.activeElements,function(t){t.restore(["fillColor"])}),e.each(this.segments,function(t){t.save()}),this.render()},removeData:function(t){var i=e.isNumber(t)?t:this.segments.length-1;this.segments.splice(i,1),this.reflow(),this.update()},reflow:function(){e.extend(this.SegmentArc.prototype,{x:this.chart.width/2,y:this.chart.height/2}),this.outerRadius=(e.min([this.chart.width,this.chart.height])-this.options.segmentStrokeWidth/2)/2,e.each(this.segments,function(t){t.update({outerRadius:this.outerRadius,innerRadius:this.outerRadius/100*this.options.percentageInnerCutout})},this)},draw:function(t){var i=t?t:1;this.clear(),e.each(this.segments,function(t,e){t.transition({circumference:this.calculateCircumference(t.value),outerRadius:this.outerRadius,innerRadius:this.outerRadius/100*this.options.percentageInnerCutout},i),t.endAngle=t.startAngle+t.circumference,t.draw(),0===e&&(t.startAngle=1.5*Math.PI),e<% for (var i=0; i
  • <%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>'};i.Type.extend({name:"Line",defaults:s,initialize:function(t){this.PointClass=i.Point.extend({strokeWidth:this.options.pointDotStrokeWidth,radius:this.options.pointDotRadius,display:this.options.pointDot,hitDetectionRadius:this.options.pointHitDetectionRadius,ctx:this.chart.ctx,inRange:function(t){return Math.pow(t-this.x,2)0&&ithis.scale.endPoint?t.controlPoints.outer.y=this.scale.endPoint:t.controlPoints.outer.ythis.scale.endPoint?t.controlPoints.inner.y=this.scale.endPoint:t.controlPoints.inner.y0&&(s.lineTo(h[h.length-1].x,this.scale.endPoint),s.lineTo(h[0].x,this.scale.endPoint),s.fillStyle=t.fillColor,s.closePath(),s.fill()),e.each(h,function(t){t.draw()})},this)}})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)",scaleBeginAtZero:!0,scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,scaleShowLine:!0,segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,legendTemplate:'
      <% for (var i=0; i
    • <%if(segments[i].label){%><%=segments[i].label%><%}%>
    • <%}%>
    '};i.Type.extend({name:"PolarArea",defaults:s,initialize:function(t){this.segments=[],this.SegmentArc=i.Arc.extend({showStroke:this.options.segmentShowStroke,strokeWidth:this.options.segmentStrokeWidth,strokeColor:this.options.segmentStrokeColor,ctx:this.chart.ctx,innerRadius:0,x:this.chart.width/2,y:this.chart.height/2}),this.scale=new i.RadialScale({display:this.options.showScale,fontStyle:this.options.scaleFontStyle,fontSize:this.options.scaleFontSize,fontFamily:this.options.scaleFontFamily,fontColor:this.options.scaleFontColor,showLabels:this.options.scaleShowLabels,showLabelBackdrop:this.options.scaleShowLabelBackdrop,backdropColor:this.options.scaleBackdropColor,backdropPaddingY:this.options.scaleBackdropPaddingY,backdropPaddingX:this.options.scaleBackdropPaddingX,lineWidth:this.options.scaleShowLine?this.options.scaleLineWidth:0,lineColor:this.options.scaleLineColor,lineArc:!0,width:this.chart.width,height:this.chart.height,xCenter:this.chart.width/2,yCenter:this.chart.height/2,ctx:this.chart.ctx,templateString:this.options.scaleLabel,valuesCount:t.length}),this.updateScaleRange(t),this.scale.update(),e.each(t,function(t,i){this.addData(t,i,!0)},this),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getSegmentsAtEvent(t):[];e.each(this.segments,function(t){t.restore(["fillColor"])}),e.each(i,function(t){t.fillColor=t.highlightColor}),this.showTooltip(i)}),this.render()},getSegmentsAtEvent:function(t){var i=[],s=e.getRelativePosition(t);return e.each(this.segments,function(t){t.inRange(s.x,s.y)&&i.push(t)},this),i},addData:function(t,i,e){var s=i||this.segments.length;this.segments.splice(s,0,new this.SegmentArc({fillColor:t.color,highlightColor:t.highlight||t.color,label:t.label,value:t.value,outerRadius:this.options.animateScale?0:this.scale.calculateCenterOffset(t.value),circumference:this.options.animateRotate?0:this.scale.getCircumference(),startAngle:1.5*Math.PI})),e||(this.reflow(),this.update())},removeData:function(t){var i=e.isNumber(t)?t:this.segments.length-1;this.segments.splice(i,1),this.reflow(),this.update()},calculateTotal:function(t){this.total=0,e.each(t,function(t){this.total+=t.value},this),this.scale.valuesCount=this.segments.length},updateScaleRange:function(t){var i=[];e.each(t,function(t){i.push(t.value)});var s=this.options.scaleOverride?{steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}:e.calculateScaleRange(i,e.min([this.chart.width,this.chart.height])/2,this.options.scaleFontSize,this.options.scaleBeginAtZero,this.options.scaleIntegersOnly);e.extend(this.scale,s,{size:e.min([this.chart.width,this.chart.height]),xCenter:this.chart.width/2,yCenter:this.chart.height/2})},update:function(){this.calculateTotal(this.segments),e.each(this.segments,function(t){t.save()}),this.reflow(),this.render()},reflow:function(){e.extend(this.SegmentArc.prototype,{x:this.chart.width/2,y:this.chart.height/2}),this.updateScaleRange(this.segments),this.scale.update(),e.extend(this.scale,{xCenter:this.chart.width/2,yCenter:this.chart.height/2}),e.each(this.segments,function(t){t.update({outerRadius:this.scale.calculateCenterOffset(t.value)})},this)},draw:function(t){var i=t||1;this.clear(),e.each(this.segments,function(t,e){t.transition({circumference:this.scale.getCircumference(),outerRadius:this.scale.calculateCenterOffset(t.value)},i),t.endAngle=t.startAngle+t.circumference,0===e&&(t.startAngle=1.5*Math.PI),e<% for (var i=0; i
  • <%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>'},initialize:function(t){this.PointClass=i.Point.extend({strokeWidth:this.options.pointDotStrokeWidth,radius:this.options.pointDotRadius,display:this.options.pointDot,hitDetectionRadius:this.options.pointHitDetectionRadius,ctx:this.chart.ctx}),this.datasets=[],this.buildScale(t),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getPointsAtEvent(t):[];this.eachPoints(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),e.each(t.datasets,function(i){var s={label:i.label||null,fillColor:i.fillColor,strokeColor:i.strokeColor,pointColor:i.pointColor,pointStrokeColor:i.pointStrokeColor,points:[]};this.datasets.push(s),e.each(i.data,function(e,n){var o;this.scale.animation||(o=this.scale.getPointPosition(n,this.scale.calculateCenterOffset(e))),s.points.push(new this.PointClass({value:e,label:t.labels[n],datasetLabel:i.label,x:this.options.animation?this.scale.xCenter:o.x,y:this.options.animation?this.scale.yCenter:o.y,strokeColor:i.pointStrokeColor,fillColor:i.pointColor,highlightFill:i.pointHighlightFill||i.pointColor,highlightStroke:i.pointHighlightStroke||i.pointStrokeColor}))},this)},this),this.render()},eachPoints:function(t){e.each(this.datasets,function(i){e.each(i.points,t,this)},this)},getPointsAtEvent:function(t){var i=e.getRelativePosition(t),s=e.getAngleFromPoint({x:this.scale.xCenter,y:this.scale.yCenter},i),n=2*Math.PI/this.scale.valuesCount,o=Math.round((s.angle-1.5*Math.PI)/n),a=[];return(o>=this.scale.valuesCount||0>o)&&(o=0),s.distance<=this.scale.drawingArea&&e.each(this.datasets,function(t){a.push(t.points[o])}),a},buildScale:function(t){this.scale=new i.RadialScale({display:this.options.showScale,fontStyle:this.options.scaleFontStyle,fontSize:this.options.scaleFontSize,fontFamily:this.options.scaleFontFamily,fontColor:this.options.scaleFontColor,showLabels:this.options.scaleShowLabels,showLabelBackdrop:this.options.scaleShowLabelBackdrop,backdropColor:this.options.scaleBackdropColor,backdropPaddingY:this.options.scaleBackdropPaddingY,backdropPaddingX:this.options.scaleBackdropPaddingX,lineWidth:this.options.scaleShowLine?this.options.scaleLineWidth:0,lineColor:this.options.scaleLineColor,angleLineColor:this.options.angleLineColor,angleLineWidth:this.options.angleShowLineOut?this.options.angleLineWidth:0,pointLabelFontColor:this.options.pointLabelFontColor,pointLabelFontSize:this.options.pointLabelFontSize,pointLabelFontFamily:this.options.pointLabelFontFamily,pointLabelFontStyle:this.options.pointLabelFontStyle,height:this.chart.height,width:this.chart.width,xCenter:this.chart.width/2,yCenter:this.chart.height/2,ctx:this.chart.ctx,templateString:this.options.scaleLabel,labels:t.labels,valuesCount:t.datasets[0].data.length}),this.scale.setScaleSize(),this.updateScaleRange(t.datasets),this.scale.buildYLabels()},updateScaleRange:function(t){var i=function(){var i=[];return e.each(t,function(t){t.data?i=i.concat(t.data):e.each(t.points,function(t){i.push(t.value)})}),i}(),s=this.options.scaleOverride?{steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}:e.calculateScaleRange(i,e.min([this.chart.width,this.chart.height])/2,this.options.scaleFontSize,this.options.scaleBeginAtZero,this.options.scaleIntegersOnly);e.extend(this.scale,s)},addData:function(t,i){this.scale.valuesCount++,e.each(t,function(t,e){var s=this.scale.getPointPosition(this.scale.valuesCount,this.scale.calculateCenterOffset(t));this.datasets[e].points.push(new this.PointClass({value:t,label:i,x:s.x,y:s.y,strokeColor:this.datasets[e].pointStrokeColor,fillColor:this.datasets[e].pointColor}))},this),this.scale.labels.push(i),this.reflow(),this.update()},removeData:function(){this.scale.valuesCount--,this.scale.labels.shift(),e.each(this.datasets,function(t){t.points.shift()},this),this.reflow(),this.update()},update:function(){this.eachPoints(function(t){t.save()}),this.reflow(),this.render()},reflow:function(){e.extend(this.scale,{width:this.chart.width,height:this.chart.height,size:e.min([this.chart.width,this.chart.height]),xCenter:this.chart.width/2,yCenter:this.chart.height/2}),this.updateScaleRange(this.datasets),this.scale.setScaleSize(),this.scale.buildYLabels()},draw:function(t){var i=t||1,s=this.chart.ctx;this.clear(),this.scale.draw(),e.each(this.datasets,function(t){e.each(t.points,function(t,e){t.hasValue()&&t.transition(this.scale.getPointPosition(e,this.scale.calculateCenterOffset(t.value)),i)},this),s.lineWidth=this.options.datasetStrokeWidth,s.strokeStyle=t.strokeColor,s.beginPath(),e.each(t.points,function(t,i){0===i?s.moveTo(t.x,t.y):s.lineTo(t.x,t.y)},this),s.closePath(),s.stroke(),s.fillStyle=t.fillColor,s.fill(),e.each(t.points,function(t){t.hasValue()&&t.draw()})},this)}})}.call(this); \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/bulb.js b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/bulb.js deleted file mode 100644 index 72c40fd7..00000000 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/bulb.js +++ /dev/null @@ -1,10 +0,0 @@ -function changeImage() { - var image = document.getElementById('myImage'); - if (image.src.match("bulb-on")) { - - image.src = "/iot/public/mydevice/images/bulb-off.png"; - } else { - - image.src = "/iot/public/mydevice/images/bulb-on.png"; - } -} \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/date-range.js b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/date-range.js deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graph_util.js b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graph_util.js index 2a2f76ba..3a5f51f7 100644 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graph_util.js +++ b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graph_util.js @@ -5,17 +5,60 @@ var currentDay = new Date(); var startDate = new Date(currentDay.getTime() - (60 * 60 * 24 * 100)); var endDate = new Date(currentDay.getTime()); -// create a custom bar renderer that has no gaps -Rickshaw.Graph.Renderer.BarNoGap = Rickshaw.Class.create(Rickshaw.Graph.Renderer.Bar, { - name: 'bar_no_gap', - barWidth: function (series) { - var frequentInterval = this._frequentInterval(series.stack); - var barWidth = this.graph.x(series.stack[0].x + frequentInterval.magnitude * 1); - return barWidth; +var groupId; + +var color = ['#c05020','#30c020', '#6060c0', '#170B3B','#5E610B', '#2F0B3A', '#FF4000', '#2F0B3A', 'steelblue']; + +// create a custom bar renderer that shift bars +Rickshaw.Graph.Renderer.BinaryBar = Rickshaw.Class.create(Rickshaw.Graph.Renderer.Bar, { + name: 'binary_bar', + render: function(args) { + + args = args || {}; + + var graph = this.graph; + var series = args.series || graph.series; + + var vis = args.vis || graph.vis; + vis.selectAll('*').remove(); + + var barWidth = this.barWidth(series.active()[0]); + var barXOffset = 0; + + var activeSeriesCount = series.filter( function(s) { return !s.disabled; } ).length; + var seriesBarWidth = this.unstack ? barWidth / activeSeriesCount : barWidth; + + var transform = function(d) { + // add a matrix transform for negative values + var matrix = [ 1, 0, 0, (d.y < 0 ? -1 : 1), 0, (d.y < 0 ? graph.y.magnitude(Math.abs(d.y)) * 2 : 0) ]; + return "matrix(" + matrix.join(',') + ")"; + }; + + var index = 0; + series.forEach( function(series) { + if (series.disabled) return; + + var nodes = vis.selectAll("path") + .data(series.stack.filter( function(d) { return d.y !== null } )) + .enter().append("svg:rect") + .attr("x", function(d) { return graph.x(d.x) + barXOffset }) + .attr("y", function(d) { return ((graph.y(index + Math.abs(d.y))) * (d.y < 0 ? -1 : 1 )) }) + .attr("width", seriesBarWidth) + .attr("height", function(d) { return graph.y.magnitude(Math.abs(d.y)) }) + .attr("transform", transform); + + index++; + Array.prototype.forEach.call(nodes[0], function(n) { + n.setAttribute('fill', series.color); + } ); + + if (this.unstack) barXOffset += seriesBarWidth; + + }, this ); } }); -function initDate(){ +function initDate() { currentDay = new Date(); startDate = new Date(currentDay.getTime() - (60 * 60 * 24 * 100)); endDate = new Date(currentDay.getTime()); @@ -44,6 +87,9 @@ var DateRange = convertDate(startDate) + " " + configObject.separator + " " + co $(document).ready(function () { initDate(); + groupId = $("#request-group-id").data("groupid"); + + $('#date-range').html(DateRange); $('#date-range').dateRangePicker(configObject) .bind('datepicker-apply', function (event, dateRange) { $(this).addClass('active'); @@ -53,16 +99,13 @@ $(document).ready(function () { getStats(fromDate, toDate); } ); + getDateTime(startDate.getTime(), endDate.getTime()); }); -$(document).ready(function () { - $('#date-range').html(DateRange); - $('#date-range').trigger('datepicker-apply', - { - 'value': DateRange, - 'date1': startDate, - 'date2': endDate - }); +//hour picker +$('#hour-btn').on('click', function () { + initDate(); + getDateTime(currentDay.getTime() - 3600000, currentDay.getTime()); }); //day picker @@ -71,12 +114,6 @@ $('#today-btn').on('click', function () { getDateTime(currentDay.getTime() - 86400000, currentDay.getTime()); }); -//hour picker -$('#hour-btn').on('click', function () { - initDate(); - getDateTime(currentDay.getTime() - 3600000, currentDay.getTime()); -}); - //week picker $('#week-btn').on('click', function () { initDate(); @@ -100,39 +137,38 @@ function getDateTime(from, to) { DateRange = convertDate(startDate) + " " + configObject.separator + " " + convertDate(endDate); console.log(DateRange); $('#date-range').html(DateRange); - $('#date-range').trigger('datepicker-apply', - { - 'value': DateRange, - 'date1': startDate, - 'date2': endDate - } - ); getStats(from / 1000, to / 1000); } function getStats(from, to) { - var deviceId = getUrlParameter('deviceId'); - var deviceType = getUrlParameter('deviceType'); - var requestData = new Object(); - - requestData['deviceId'] = deviceId; - requestData['deviceType'] = deviceType; - + var getStatsRequest; if (from) { requestData['from'] = from; } - if (to) { requestData['to'] = to; } + if (groupId && groupId != "0") { + requestData['groupId'] = groupId; + getStatsRequest = $.ajax({ + url: "api/stats/group", + method: "GET", + data: requestData + }); + } else { + var deviceId = getUrlParameter('deviceId'); + var deviceType = getUrlParameter('deviceType'); - var getStatsRequest = $.ajax({ - url: "../api/stats", - method: "GET", - data: requestData - }); + requestData['deviceId'] = deviceId; + requestData['deviceType'] = deviceType; + getStatsRequest = $.ajax({ + url: "api/stats", + method: "GET", + data: requestData + }); + } getStatsRequest.done(function (stats) { updateGraphs(JSON.parse(stats)); }); @@ -140,7 +176,6 @@ function getStats(from, to) { getStatsRequest.fail(function (jqXHR, textStatus) { alert("Request failed: " + textStatus); }); - } function getUrlParameter(paramName) { @@ -160,7 +195,7 @@ function updateGraphs(stats) { var temperatureData = stats['temperatureData']; if (typeof temperatureData != 'undefined') { $('#div-temperatureData').html("").html("

    Temperature



    "); - updateTemperatureGraph(convertStatsToGraphData(temperatureData)); + drawLineGraph(1, temperatureData); } else { $('#div-temperatureData').html(""); } @@ -168,7 +203,7 @@ function updateGraphs(stats) { var lightData = stats['lightData']; if (typeof lightData != 'undefined') { $('#div-lightData').html("").html("

    Light



    "); - updateLightGraph(convertStatsToGraphData(lightData)); + drawBarGraph(2, lightData); } else { $('#div-lightData').html(""); } @@ -176,7 +211,7 @@ function updateGraphs(stats) { var motionData = stats['motionData']; if (typeof motionData != 'undefined') { $('#div-motionData').html("").html("

    Motion



    "); - updateMotionGraph(convertStatsToGraphData(motionData)); + drawBarGraph(3, motionData); } else { $('#div-motionData').html(""); } @@ -184,7 +219,7 @@ function updateGraphs(stats) { var sonarData = stats['sonarData']; if (typeof sonarData != 'undefined') { $('#div-sonarData').html("").html("

    Sonar



    "); - updateSonarGraph(convertStatsToGraphData(sonarData)); + drawLineGraph(4, sonarData); } else { $('#div-sonarData').html(""); } @@ -192,7 +227,7 @@ function updateGraphs(stats) { var fanData = stats['fanData']; if (typeof fanData != 'undefined') { $('#div-fanData').html("").html("

    Fan Status



    "); - updateFanGraph(convertStateStatsToGraphData(fanData)); + drawBarGraph(5, fanData); } else { $('#div-fanData').html(""); } @@ -200,7 +235,7 @@ function updateGraphs(stats) { var bulbData = stats['bulbData']; if (typeof bulbData != 'undefined') { $('#div-bulbData').html("").html("

    Bulb Status



    "); - updateBulbGraph(convertStateStatsToGraphData(bulbData)); + drawBarGraph(6, bulbData); } else { $('#div-bulbData').html(""); } @@ -208,7 +243,7 @@ function updateGraphs(stats) { var cpuData = stats['cpuData']; if (typeof cpuData != 'undefined') { $('#div-CPUData').html("").html("

    CPU Load



    "); - updateCPUGraph(convertStateStatsToGraphData(cpuData)); + drawLineGraph(7, cpuData); } else { $('#div-CPUData').html(""); } @@ -216,7 +251,7 @@ function updateGraphs(stats) { var ramData = stats['ramData']; if (typeof ramData != 'undefined') { $('#div-RAMData').html("").html("

    RAM Usage



    "); - updateRAMGraph(convertStateStatsToGraphData(ramData)); + drawLineGraph(8, ramData); } else { $('#div-RAMData').html(""); } @@ -224,7 +259,7 @@ function updateGraphs(stats) { var cpuTemperatureData = stats['cpuTemperatureData']; if (typeof cpuTemperatureData != 'undefined') { $('#div-cpuTemperatureData').html("").html("

    CPU Temperature



    "); - updateCPUTemperatureGraph(convertStatsToGraphData(cpuTemperatureData)); + drawLineGraph(9, cpuTemperatureData); } else { $('#div-cpuTemperatureData').html(""); } @@ -232,20 +267,187 @@ function updateGraphs(stats) { scaleGraphs(); } +function drawLineGraph(graphId, chartDataRaw){ + var chartWrapperElmId = "#canvas-wrapper" + graphId; + var graphWidth = $(chartWrapperElmId).width() - 50; + if (chartDataRaw.length == 0) { + $(chartWrapperElmId).html("No data available..."); + return; + } + + var chartDiv = "chart" + graphId; + var sliderDiv = "slider" + graphId; + var x_axis = "x_axis" + graphId; + var y_axis = "y_axis" + graphId; + $(chartWrapperElmId).html("").html('
    '); + + var graphConfig = { + element: document.getElementById(chartDiv), + width: graphWidth, + height: 400, + strokeWidth: 1, + renderer: 'line', + unstack: true, + stack: false, + xScale: d3.time.scale(), + padding: {top: 0.2, left: 0.02, right: 0.02, bottom: 0}, + series:[] + }; + + var k = 0; + for (var i = 0; i < chartDataRaw.length; i++){ + var chartData = []; + if (chartDataRaw[i].stats.length > 0){ + for (var j = 0; j < chartDataRaw[i].stats.length; j++){ + chartData.push({x:parseInt(chartDataRaw[i].stats[j].x), y:parseInt(chartDataRaw[i].stats[j].y)}); + } + graphConfig['series'].push({ 'color': color[k], 'data': chartData, 'name': chartDataRaw[i].device }); + } + if (++k == color.length){ + k = 0; + } + } + + if (graphConfig['series'].length == 0) { + $(chartWrapperElmId).html("No data available..."); + return; + } + + var graph = new Rickshaw.Graph(graphConfig); + + graph.render(); + + var hoverDetail = new Rickshaw.Graph.HoverDetail( { + graph: graph + } ); + + var xAxis = new Rickshaw.Graph.Axis.X({ + graph: graph, + orientation: 'bottom', + element: document.getElementById(x_axis), + tickFormat: graph.x.tickFormat() + }); + + xAxis.render(); + + var yAxis = new Rickshaw.Graph.Axis.Y({ + graph: graph, + orientation: 'left', + element: document.getElementById(y_axis), + width: 40, + height: 410 + }); + + yAxis.render(); + + var slider = new Rickshaw.Graph.RangeSlider.Preview({ + graph: graph, + element: document.getElementById(sliderDiv) + }); +} + + +function drawBarGraph(graphId, chartDataRaw){ + var chartWrapperElmId = "#canvas-wrapper" + graphId; + var graphWidth = $(chartWrapperElmId).width() - 50; + if (chartDataRaw.length == 0) { + $(chartWrapperElmId).html("No data available..."); + return; + } + + var chartDiv = "chart" + graphId; + var sliderDiv = "slider" + graphId; + var x_axis = "x_axis" + graphId; + var y_axis = "y_axis" + graphId; + $(chartWrapperElmId).html("").html('
    '); + + var graphConfig = { + element: document.getElementById(chartDiv), + width: graphWidth, + height: 150, + strokeWidth: 0.5, + renderer: 'binary_bar', + offset: 'zero', + xScale: d3.time.scale(), + padding: {top: 0.2, left: 0.02, right: 0.02, bottom: 0}, + series:[] + }; + + var k = 0; + for (var i = 0; i < chartDataRaw.length; i++){ + var chartData = []; + if (chartDataRaw[i].stats.length > 0){ + for (var j = 0; j < chartDataRaw[i].stats.length; j++){ + chartData.push({x:parseInt(chartDataRaw[i].stats[j].x), y:parseInt(chartDataRaw[i].stats[j].y)}); + } + graphConfig['series'].push({ 'color': color[k], 'data': chartData, 'name': chartDataRaw[i].device }); + } + if (++k == color.length){ + k = 0; + } + } + + if (graphConfig['series'].length == 0) { + $(chartWrapperElmId).html("No data available..."); + return; + } + + var graph = new Rickshaw.Graph(graphConfig); + + graph.registerRenderer(new Rickshaw.Graph.Renderer.BinaryBar({graph: graph})); + + graph.render(); + + var xAxis = new Rickshaw.Graph.Axis.X({ + graph: graph, + orientation: 'bottom', + element: document.getElementById(x_axis), + tickFormat: graph.x.tickFormat() + }); + + xAxis.render(); + + var yAxis = new Rickshaw.Graph.Axis.Y({ + graph: graph, + orientation: 'left', + element: document.getElementById(y_axis), + width: 40, + height: 160, + tickFormat: function (y) { + return ''; + } + }); + + yAxis.render(); + + var slider = new Rickshaw.Graph.RangeSlider.Preview({ + graph: graph, + element: document.getElementById(sliderDiv) + }); +} + function scaleGraphs() { var sliders = $('.right_handle'); if (sliders.length == 0) { return; } var graphWidth = 0; - for (var i = 1; i < 10; i++){ - if ($('#canvas-wrapper' + i).length){ + for (var i = 1; i < 10; i++) { + if ($('#canvas-wrapper' + i).length) { graphWidth = $('#canvas-wrapper' + i).width() - 50; break; } } - if (graphWidth <= 0){ + if (graphWidth <= 0) { return; } @@ -272,42 +474,6 @@ function scaleGraphs() { } } -function convertStatsToGraphData(stats) { - - var graphData = new Array(); - if (!stats) { - return graphData; - } - for (var i = 0; i < stats.length; i++) { - graphData.push({x: parseInt(stats[i]['time']) * 1000, y: stats[i]['value']}) - } - - return graphData; -} - - -function convertStateStatsToGraphData(stats) { - - var graphData = new Array(); - if (!stats) { - return graphData; - } - var yValue; - for (var i = 0; i < stats.length; i++) { - yValue = -1; - - if (stats[i]['value'].toUpperCase() == 'ON') { - yValue = 1; - } else if (stats[i]['value'].toUpperCase() == 'OFF') { - yValue = 0; - } - - graphData.push({x: parseInt(stats[i]['time']) * 1000, y: yValue}) - } - - return graphData; -} - function convertDate(date) { var month = date.getMonth() + 1; var day = date.getDate(); diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/bulb_graph.js b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/bulb_graph.js deleted file mode 100644 index f4a1c5a4..00000000 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/bulb_graph.js +++ /dev/null @@ -1,85 +0,0 @@ -function updateBulbGraph(bulbData) { - renderBulbChart(bulbData); -} - - -function renderBulbChart(chartDataRaw){ - var chartWrapperElmId = "#canvas-wrapper6"; - var graphWidth = $(chartWrapperElmId).width() - 50; - if (chartDataRaw.length == 0) { - $(chartWrapperElmId).html("No data available..."); - return; - } - - var chartData = [[], []]; - for (var i = 0; i < chartDataRaw.length; i++){ - chartData[0].push({x:parseInt(chartDataRaw[i].x), y:parseInt(chartDataRaw[i].y)}); - chartData[1].push({x:parseInt(chartDataRaw[i].x), y:Math.abs(parseInt(chartDataRaw[i].y) - 1)}); - } - - //var i = parseInt(fromDate); - //while (i < parseInt(toDate)) { - // var rnd = Math.round(Math.random()); - // chartData[0].push({x: i * 1000, y: rnd}); - // chartData[1].push({x: i * 1000, y: Math.abs(rnd - 1)}); - // i += 60 * 5; - //} - - var chartDiv = "chart6"; - var sliderDiv = "slider6"; - var x_axis = "x_axis6"; - var y_axis = "y_axis6"; - $(chartWrapperElmId).html("").html('
    '); - - var graph = new Rickshaw.Graph({ - element: document.getElementById(chartDiv), - width: graphWidth, - height: 150, - strokeWidth: 0.5, - renderer: 'bar_no_gap', - xScale: d3.time.scale(), - padding: {top: 0.2, left: 0.02, right: 0.02, bottom: 0}, - series: [ - {color: '#5E610B', data: chartData[0]}, - {color: 'white', data: chartData[1]} - ] - }); - - graph.registerRenderer(new Rickshaw.Graph.Renderer.BarNoGap({graph: graph})); - - graph.render(); - - var xAxis = new Rickshaw.Graph.Axis.X({ - graph: graph, - orientation: 'bottom', - element: document.getElementById(x_axis), - tickFormat: graph.x.tickFormat() - }); - - xAxis.render(); - - var yAxis = new Rickshaw.Graph.Axis.Y({ - graph: graph, - orientation: 'left', - element: document.getElementById(y_axis), - width: 40, - height: 160, - tickFormat: function (y) { - switch (y) { - case 1: - return 'ON'; - case 0: - return 'OFF'; - default : - return ''; - } - } - }); - - yAxis.render(); - - var slider = new Rickshaw.Graph.RangeSlider.Preview({ - graph: graph, - element: document.getElementById(sliderDiv) - }); -} \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/cpu_graph.js b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/cpu_graph.js deleted file mode 100644 index cdaf9b2a..00000000 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/cpu_graph.js +++ /dev/null @@ -1,69 +0,0 @@ -function updateCPUGraph(CPUData) { - renderCPUChart(CPUData); -} - -function renderCPUChart(chartDataRaw){ - var chartWrapperElmId = "#canvas-wrapper7"; - var graphWidth = $(chartWrapperElmId).width() - 50; - if (chartDataRaw.length == 0) { - $(chartWrapperElmId).html("No data available..."); - return; - } - - var chartData = []; - for (var i = 0; i < chartDataRaw.length; i++){ - chartData.push({x:parseInt(chartDataRaw[i].x), y:parseInt(chartDataRaw[i].y)}); - } - - //var i = parseInt(fromDate); - //while (i < parseInt(toDate)){ - // var rnd = Math.random() * (30 - 20) + 20; - // chartData.push({x:i * 1000, y:rnd}); - // i += 60 * 5; - //} - - var chartDiv = "chart7"; - var sliderDiv = "slider7"; - var x_axis = "x_axis7"; - var y_axis = "y_axis7"; - $(chartWrapperElmId).html("").html('
    '); - - var graph = new Rickshaw.Graph({ - element: document.getElementById(chartDiv), - width: graphWidth, - height: 400, - strokeWidth: 1, - renderer: 'line', - xScale: d3.time.scale(), - padding: {top: 0.2, left: 0.02, right: 0.02, bottom: 0}, - series:[ - { color: '#2F0B3A', data: chartData } - ] - }); - - graph.render(); - - var xAxis = new Rickshaw.Graph.Axis.X({ - graph: graph, - orientation: 'bottom', - element: document.getElementById(x_axis), - tickFormat: graph.x.tickFormat() - }); - - xAxis.render(); - - var yAxis = new Rickshaw.Graph.Axis.Y({ - graph: graph, - orientation: 'left', - element: document.getElementById(y_axis), - width: 40, - height: 410 - }); - - yAxis.render(); - - var slider = new Rickshaw.Graph.RangeSlider.Preview({ - graph: graph, - element: document.getElementById(sliderDiv) - }); -} \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/cpu_temperature_graph.js b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/cpu_temperature_graph.js deleted file mode 100644 index cc381c96..00000000 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/cpu_temperature_graph.js +++ /dev/null @@ -1,70 +0,0 @@ -function updateCPUTemperatureGraph(temperatureData) { - console.log("cpuTemperatureData"); - renderCPUTemperatureChart(temperatureData); -} - -function renderCPUTemperatureChart(chartDataRaw) { - var chartWrapperElmId = "#canvas-wrapper9"; - var graphWidth = $(chartWrapperElmId).width() - 50; - if (chartDataRaw.length == 0) { - $(chartWrapperElmId).html("No data available..."); - return; - } - - var chartData = []; - for (var i = 0; i < chartDataRaw.length; i++) { - chartData.push({x: parseInt(chartDataRaw[i].x), y: parseInt(chartDataRaw[i].y)}); - } - - //var i = parseInt(fromDate); - //while (i < parseInt(toDate)){ - // var rnd = Math.random() * (30 - 20) + 20; - // chartData.push({x:i * 1000, y:rnd}); - // i += 60 * 5; - //} - - var chartDiv = "chart9"; - var sliderDiv = "slider9"; - var x_axis = "x_axis9"; - var y_axis = "y_axis9"; - $(chartWrapperElmId).html("").html('
    '); - - var graph = new Rickshaw.Graph({ - element: document.getElementById(chartDiv), - width: graphWidth, - height: 400, - strokeWidth: 1, - renderer: 'line', - xScale: d3.time.scale(), - padding: {top: 0.2, left: 0.02, right: 0.02, bottom: 0}, - series: [ - {color: '#FF4000', data: chartData} - ] - }); - - graph.render(); - - var xAxis = new Rickshaw.Graph.Axis.X({ - graph: graph, - orientation: 'bottom', - element: document.getElementById(x_axis), - tickFormat: graph.x.tickFormat() - }); - - xAxis.render(); - - var yAxis = new Rickshaw.Graph.Axis.Y({ - graph: graph, - orientation: 'left', - element: document.getElementById(y_axis), - width: 40, - height: 410 - }); - - yAxis.render(); - - var slider = new Rickshaw.Graph.RangeSlider.Preview({ - graph: graph, - element: document.getElementById(sliderDiv) - }); -} \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/fan_graph.js b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/fan_graph.js deleted file mode 100644 index 991a14d6..00000000 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/fan_graph.js +++ /dev/null @@ -1,84 +0,0 @@ -function updateFanGraph(fanData) { - renderFanChart(fanData); -} - -function renderFanChart(chartDataRaw) { - var chartWrapperElmId = "#canvas-wrapper5"; - var graphWidth = $(chartWrapperElmId).width() - 50; - if (chartDataRaw.length == 0) { - $(chartWrapperElmId).html("No data available..."); - return; - } - - var chartData = [[], []]; - for (var i = 0; i < chartDataRaw.length; i++){ - chartData[0].push({x:parseInt(chartDataRaw[i].x), y:parseInt(chartDataRaw[i].y)}); - chartData[1].push({x:parseInt(chartDataRaw[i].x), y:Math.abs(parseInt(chartDataRaw[i].y) - 1)}); - } - - //var i = parseInt(fromDate); - //while (i < parseInt(toDate)) { - // var rnd = Math.round(Math.random()); - // chartData[0].push({x: i * 1000, y: rnd}); - // chartData[1].push({x: i * 1000, y: Math.abs(rnd - 1)}); - // i += 60 * 5; - //} - - var chartDiv = "chart5"; - var sliderDiv = "slider5"; - var x_axis = "x_axis5"; - var y_axis = "y_axis5"; - $(chartWrapperElmId).html("").html('
    '); - - var graph = new Rickshaw.Graph({ - element: document.getElementById(chartDiv), - width: graphWidth, - height: 150, - strokeWidth: 0.5, - renderer: 'bar_no_gap', - xScale: d3.time.scale(), - padding: {top: 0.2, left: 0.02, right: 0.02, bottom: 0}, - series: [ - {color: '#2F0B3A', data: chartData[0]}, - {color: 'white', data: chartData[1]} - ] - }); - - graph.registerRenderer(new Rickshaw.Graph.Renderer.BarNoGap({graph: graph})); - - graph.render(); - - var xAxis = new Rickshaw.Graph.Axis.X({ - graph: graph, - orientation: 'bottom', - element: document.getElementById(x_axis), - tickFormat: graph.x.tickFormat() - }); - - xAxis.render(); - - var yAxis = new Rickshaw.Graph.Axis.Y({ - graph: graph, - orientation: 'left', - element: document.getElementById(y_axis), - width: 40, - height: 160, - tickFormat: function (y) { - switch (y) { - case 1: - return 'ON'; - case 0: - return 'OFF'; - default : - return ''; - } - } - }); - - yAxis.render(); - - var slider = new Rickshaw.Graph.RangeSlider.Preview({ - graph: graph, - element: document.getElementById(sliderDiv) - }); -} \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/light_graph.js b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/light_graph.js deleted file mode 100644 index 1e91a758..00000000 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/light_graph.js +++ /dev/null @@ -1,85 +0,0 @@ -function updateLightGraph(lightData) { - renderLightChart(lightData); -} - - -function renderLightChart(chartDataRaw){ - var chartWrapperElmId = "#canvas-wrapper2"; - var graphWidth = $(chartWrapperElmId).width() - 50; - if (chartDataRaw.length == 0) { - $(chartWrapperElmId).html("No data available..."); - return; - } - - var chartData = [[], []]; - for (var i = 0; i < chartDataRaw.length; i++){ - chartData[0].push({x:parseInt(chartDataRaw[i].x), y:parseInt(chartDataRaw[i].y)}); - chartData[1].push({x:parseInt(chartDataRaw[i].x), y:Math.abs(parseInt(chartDataRaw[i].y) - 1)}); - } - - //var i = parseInt(fromDate); - //while (i < parseInt(toDate)) { - // var rnd = Math.round(Math.random()); - // chartData[0].push({x: i * 1000, y: rnd}); - // chartData[1].push({x: i * 1000, y: Math.abs(rnd - 1)}); - // i += 60 * 5; - //} - - var chartDiv = "chart2"; - var sliderDiv = "slider2"; - var x_axis = "x_axis2"; - var y_axis = "y_axis2"; - $(chartWrapperElmId).html("").html('
    '); - - var graph = new Rickshaw.Graph({ - element: document.getElementById(chartDiv), - width: graphWidth, - height: 150, - strokeWidth: 0.5, - renderer: 'bar_no_gap', - xScale: d3.time.scale(), - padding: {top: 0.2, left: 0.02, right: 0.02, bottom: 0}, - series: [ - {color: 'steelblue', data: chartData[0]}, - {color: 'white', data: chartData[1]} - ] - }); - - graph.registerRenderer(new Rickshaw.Graph.Renderer.BarNoGap({graph: graph})); - - graph.render(); - - var xAxis = new Rickshaw.Graph.Axis.X({ - graph: graph, - orientation: 'bottom', - element: document.getElementById(x_axis), - tickFormat: graph.x.tickFormat() - }); - - xAxis.render(); - - var yAxis = new Rickshaw.Graph.Axis.Y({ - graph: graph, - orientation: 'left', - element: document.getElementById(y_axis), - width: 40, - height: 160, - tickFormat: function (y) { - switch (y) { - case 1: - return 'ON'; - case 0: - return 'OFF'; - default : - return ''; - } - } - }); - - yAxis.render(); - - var slider = new Rickshaw.Graph.RangeSlider.Preview({ - graph: graph, - element: document.getElementById(sliderDiv) - }); -} \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/motion_graph.js b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/motion_graph.js deleted file mode 100644 index 04fe84d6..00000000 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/motion_graph.js +++ /dev/null @@ -1,70 +0,0 @@ -function updateMotionGraph(motionData) { - renderMotionChart(motionData); -} - - -function renderMotionChart(chartDataRaw){ - var chartWrapperElmId = "#canvas-wrapper3"; - var graphWidth = $(chartWrapperElmId).width() - 50; - if (chartDataRaw.length == 0) { - $(chartWrapperElmId).html("No data available..."); - return; - } - - var chartData = []; - for (var i = 0; i < chartDataRaw.length; i++){ - chartData.push({x:parseInt(chartDataRaw[i].x), y:parseInt(chartDataRaw[i].y)}); - } - - //var i = parseInt(fromDate); - //while (i < parseInt(toDate)){ - // var rnd = Math.random() * (30 - 20) + 20; - // chartData.push({x:i * 1000, y:rnd}); - // i += 60 * 5; - //} - - var chartDiv = "chart3"; - var sliderDiv = "slider3"; - var x_axis = "x_axis3"; - var y_axis = "y_axis3"; - $(chartWrapperElmId).html("").html('
    '); - - var graph = new Rickshaw.Graph({ - element: document.getElementById(chartDiv), - width: graphWidth, - height: 400, - strokeWidth: 0.5, - renderer: 'line', - xScale: d3.time.scale(), - padding: {top: 0.2, left: 0.02, right: 0.02, bottom: 0}, - series:[ - { color: '#2F0B3A', data: chartData } - ] - }); - - graph.render(); - - var xAxis = new Rickshaw.Graph.Axis.X({ - graph: graph, - orientation: 'bottom', - element: document.getElementById(x_axis), - tickFormat: graph.x.tickFormat() - }); - - xAxis.render(); - - var yAxis = new Rickshaw.Graph.Axis.Y({ - graph: graph, - orientation: 'left', - element: document.getElementById(y_axis), - width: 40, - height: 410 - }); - - yAxis.render(); - - var slider = new Rickshaw.Graph.RangeSlider.Preview({ - graph: graph, - element: document.getElementById(sliderDiv) - }); -} \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/ram_graph.js b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/ram_graph.js deleted file mode 100644 index 5b6953e8..00000000 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/ram_graph.js +++ /dev/null @@ -1,69 +0,0 @@ -function updateRAMGraph(RAMData) { - renderRAMChart(RAMData); -} - -function renderRAMChart(chartDataRaw){ - var chartWrapperElmId = "#canvas-wrapper8"; - var graphWidth = $(chartWrapperElmId).width() - 50; - if (chartDataRaw.length == 0) { - $(chartWrapperElmId).html("No data available..."); - return; - } - - var chartData = []; - for (var i = 0; i < chartDataRaw.length; i++){ - chartData.push({x:parseInt(chartDataRaw[i].x), y:parseInt(chartDataRaw[i].y)}); - } - - //var i = parseInt(fromDate); - //while (i < parseInt(toDate)){ - // var rnd = Math.random() * (30 - 20) + 20; - // chartData.push({x:i * 1000, y:rnd}); - // i += 60 * 5; - //} - - var chartDiv = "chart8"; - var sliderDiv = "slider8"; - var x_axis = "x_axis8"; - var y_axis = "y_axis8"; - $(chartWrapperElmId).html("").html('
    '); - - var graph = new Rickshaw.Graph({ - element: document.getElementById(chartDiv), - width: graphWidth, - height: 400, - strokeWidth: 1, - renderer: 'line', - xScale: d3.time.scale(), - padding: {top: 0.2, left: 0.02, right: 0.02, bottom: 0}, - series:[ - { color: '#170B3B', data: chartData } - ] - }); - - graph.render(); - - var xAxis = new Rickshaw.Graph.Axis.X({ - graph: graph, - orientation: 'bottom', - element: document.getElementById(x_axis), - tickFormat: graph.x.tickFormat() - }); - - xAxis.render(); - - var yAxis = new Rickshaw.Graph.Axis.Y({ - graph: graph, - orientation: 'left', - element: document.getElementById(y_axis), - width: 40, - height: 410 - }); - - yAxis.render(); - - var slider = new Rickshaw.Graph.RangeSlider.Preview({ - graph: graph, - element: document.getElementById(sliderDiv) - }); -} \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/sonar_graph.js b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/sonar_graph.js deleted file mode 100644 index c7bdf5c5..00000000 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/sonar_graph.js +++ /dev/null @@ -1,69 +0,0 @@ -function updateSonarGraph(sonarData) { - renderSonarChart(sonarData); -} - -function renderSonarChart(chartDataRaw){ - var chartWrapperElmId = "#canvas-wrapper4"; - var graphWidth = $(chartWrapperElmId).width() - 50; - if (chartDataRaw.length == 0) { - $(chartWrapperElmId).html("No data available..."); - return; - } - - var chartData = []; - for (var i = 0; i < chartDataRaw.length; i++){ - chartData.push({x:parseInt(chartDataRaw[i].x), y:parseInt(chartDataRaw[i].y)}); - } - - //var i = parseInt(fromDate); - //while (i < parseInt(toDate)){ - // var rnd = Math.random() * (30 - 20) + 20; - // chartData.push({x:i * 1000, y:rnd}); - // i += 60 * 5; - //} - - var chartDiv = "chart4"; - var sliderDiv = "slider4"; - var x_axis = "x_axis4"; - var y_axis = "y_axis4"; - $(chartWrapperElmId).html("").html('
    '); - - var graph = new Rickshaw.Graph({ - element: document.getElementById(chartDiv), - width: graphWidth, - height: 400, - strokeWidth: 1, - renderer: 'line', - xScale: d3.time.scale(), - padding: {top: 0.2, left: 0.02, right: 0.02, bottom: 0}, - series:[ - { color: '#170B3B', data: chartData } - ] - }); - - graph.render(); - - var xAxis = new Rickshaw.Graph.Axis.X({ - graph: graph, - orientation: 'bottom', - element: document.getElementById(x_axis), - tickFormat: graph.x.tickFormat() - }); - - xAxis.render(); - - var yAxis = new Rickshaw.Graph.Axis.Y({ - graph: graph, - orientation: 'left', - element: document.getElementById(y_axis), - width: 40, - height: 410 - }); - - yAxis.render(); - - var slider = new Rickshaw.Graph.RangeSlider.Preview({ - graph: graph, - element: document.getElementById(sliderDiv) - }); -} \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/temperature_graph.js b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/temperature_graph.js deleted file mode 100644 index 3fed9611..00000000 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/graphs/temperature_graph.js +++ /dev/null @@ -1,70 +0,0 @@ -function updateTemperatureGraph(temperatureData) { - console.log("temperatureData"); - renderTemperatureChart(temperatureData); -} - -function renderTemperatureChart(chartDataRaw){ - var chartWrapperElmId = "#canvas-wrapper1"; - var graphWidth = $(chartWrapperElmId).width() - 50; - if (chartDataRaw.length == 0) { - $(chartWrapperElmId).html("No data available..."); - return; - } - - var chartData = []; - for (var i = 0; i < chartDataRaw.length; i++){ - chartData.push({x:parseInt(chartDataRaw[i].x), y:parseInt(chartDataRaw[i].y)}); - } - - //var i = parseInt(fromDate); - //while (i < parseInt(toDate)){ - // var rnd = Math.random() * (30 - 20) + 20; - // chartData.push({x:i * 1000, y:rnd}); - // i += 60 * 5; - //} - - var chartDiv = "chart1"; - var sliderDiv = "slider1"; - var x_axis = "x_axis1"; - var y_axis = "y_axis1"; - $(chartWrapperElmId).html("").html('
    '); - - var graph = new Rickshaw.Graph({ - element: document.getElementById(chartDiv), - width: graphWidth, - height: 400, - strokeWidth: 1, - renderer: 'line', - xScale: d3.time.scale(), - padding: {top: 0.2, left: 0.02, right: 0.02, bottom: 0}, - series:[ - { color: '#FF4000', data: chartData } - ] - }); - - graph.render(); - - var xAxis = new Rickshaw.Graph.Axis.X({ - graph: graph, - orientation: 'bottom', - element: document.getElementById(x_axis), - tickFormat: graph.x.tickFormat() - }); - - xAxis.render(); - - var yAxis = new Rickshaw.Graph.Axis.Y({ - graph: graph, - orientation: 'left', - element: document.getElementById(y_axis), - width: 40, - height: 410 - }); - - yAxis.render(); - - var slider = new Rickshaw.Graph.RangeSlider.Preview({ - graph: graph, - element: document.getElementById(sliderDiv) - }); -} \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/linewithfocuschart.js b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/linewithfocuschart.js deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/moment.js b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/moment.js deleted file mode 100644 index a9363815..00000000 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/moment.js +++ /dev/null @@ -1,2808 +0,0 @@ -//! moment.js -//! version : 2.8.1 -//! authors : Tim Wood, Iskren Chernev, Moment.js contributors -//! license : MIT -//! momentjs.com - -(function (undefined) { - /************************************ - Constants - ************************************/ - - var moment, - VERSION = '2.8.1', - // the global-scope this is NOT the global object in Node.js - globalScope = typeof global !== 'undefined' ? global : this, - oldGlobalMoment, - round = Math.round, - i, - - YEAR = 0, - MONTH = 1, - DATE = 2, - HOUR = 3, - MINUTE = 4, - SECOND = 5, - MILLISECOND = 6, - - // internal storage for locale config files - locales = {}, - - // extra moment internal properties (plugins register props here) - momentProperties = [], - - // check for nodeJS - hasModule = (typeof module !== 'undefined' && module.exports), - - // ASP.NET json date format regex - aspNetJsonRegex = /^\/?Date\((\-?\d+)/i, - aspNetTimeSpanJsonRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/, - - // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html - // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere - isoDurationRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/, - - // format tokens - formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g, - localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g, - - // parsing token regexes - parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99 - parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999 - parseTokenOneToFourDigits = /\d{1,4}/, // 0 - 9999 - parseTokenOneToSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999 - parseTokenDigits = /\d+/, // nonzero number of digits - parseTokenWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i, // any word (or two) characters or numbers including two/three word month in arabic. - parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z - parseTokenT = /T/i, // T (ISO separator) - parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123 - parseTokenOrdinal = /\d{1,2}/, - - //strict parsing regexes - parseTokenOneDigit = /\d/, // 0 - 9 - parseTokenTwoDigits = /\d\d/, // 00 - 99 - parseTokenThreeDigits = /\d{3}/, // 000 - 999 - parseTokenFourDigits = /\d{4}/, // 0000 - 9999 - parseTokenSixDigits = /[+-]?\d{6}/, // -999,999 - 999,999 - parseTokenSignedNumber = /[+-]?\d+/, // -inf - inf - - // iso 8601 regex - // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) - isoRegex = /^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/, - - isoFormat = 'YYYY-MM-DDTHH:mm:ssZ', - - isoDates = [ - ['YYYYYY-MM-DD', /[+-]\d{6}-\d{2}-\d{2}/], - ['YYYY-MM-DD', /\d{4}-\d{2}-\d{2}/], - ['GGGG-[W]WW-E', /\d{4}-W\d{2}-\d/], - ['GGGG-[W]WW', /\d{4}-W\d{2}/], - ['YYYY-DDD', /\d{4}-\d{3}/] - ], - - // iso time formats and regexes - isoTimes = [ - ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d+/], - ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/], - ['HH:mm', /(T| )\d\d:\d\d/], - ['HH', /(T| )\d\d/] - ], - - // timezone chunker "+10:00" > ["10", "00"] or "-1530" > ["-15", "30"] - parseTimezoneChunker = /([\+\-]|\d\d)/gi, - - // getter and setter names - proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'), - unitMillisecondFactors = { - 'Milliseconds' : 1, - 'Seconds' : 1e3, - 'Minutes' : 6e4, - 'Hours' : 36e5, - 'Days' : 864e5, - 'Months' : 2592e6, - 'Years' : 31536e6 - }, - - unitAliases = { - ms : 'millisecond', - s : 'second', - m : 'minute', - h : 'hour', - d : 'day', - D : 'date', - w : 'week', - W : 'isoWeek', - M : 'month', - Q : 'quarter', - y : 'year', - DDD : 'dayOfYear', - e : 'weekday', - E : 'isoWeekday', - gg: 'weekYear', - GG: 'isoWeekYear' - }, - - camelFunctions = { - dayofyear : 'dayOfYear', - isoweekday : 'isoWeekday', - isoweek : 'isoWeek', - weekyear : 'weekYear', - isoweekyear : 'isoWeekYear' - }, - - // format function strings - formatFunctions = {}, - - // default relative time thresholds - relativeTimeThresholds = { - s: 45, // seconds to minute - m: 45, // minutes to hour - h: 22, // hours to day - d: 26, // days to month - M: 11 // months to year - }, - - // tokens to ordinalize and pad - ordinalizeTokens = 'DDD w W M D d'.split(' '), - paddedTokens = 'M D H h m s w W'.split(' '), - - formatTokenFunctions = { - M : function () { - return this.month() + 1; - }, - MMM : function (format) { - return this.localeData().monthsShort(this, format); - }, - MMMM : function (format) { - return this.localeData().months(this, format); - }, - D : function () { - return this.date(); - }, - DDD : function () { - return this.dayOfYear(); - }, - d : function () { - return this.day(); - }, - dd : function (format) { - return this.localeData().weekdaysMin(this, format); - }, - ddd : function (format) { - return this.localeData().weekdaysShort(this, format); - }, - dddd : function (format) { - return this.localeData().weekdays(this, format); - }, - w : function () { - return this.week(); - }, - W : function () { - return this.isoWeek(); - }, - YY : function () { - return leftZeroFill(this.year() % 100, 2); - }, - YYYY : function () { - return leftZeroFill(this.year(), 4); - }, - YYYYY : function () { - return leftZeroFill(this.year(), 5); - }, - YYYYYY : function () { - var y = this.year(), sign = y >= 0 ? '+' : '-'; - return sign + leftZeroFill(Math.abs(y), 6); - }, - gg : function () { - return leftZeroFill(this.weekYear() % 100, 2); - }, - gggg : function () { - return leftZeroFill(this.weekYear(), 4); - }, - ggggg : function () { - return leftZeroFill(this.weekYear(), 5); - }, - GG : function () { - return leftZeroFill(this.isoWeekYear() % 100, 2); - }, - GGGG : function () { - return leftZeroFill(this.isoWeekYear(), 4); - }, - GGGGG : function () { - return leftZeroFill(this.isoWeekYear(), 5); - }, - e : function () { - return this.weekday(); - }, - E : function () { - return this.isoWeekday(); - }, - a : function () { - return this.localeData().meridiem(this.hours(), this.minutes(), true); - }, - A : function () { - return this.localeData().meridiem(this.hours(), this.minutes(), false); - }, - H : function () { - return this.hours(); - }, - h : function () { - return this.hours() % 12 || 12; - }, - m : function () { - return this.minutes(); - }, - s : function () { - return this.seconds(); - }, - S : function () { - return toInt(this.milliseconds() / 100); - }, - SS : function () { - return leftZeroFill(toInt(this.milliseconds() / 10), 2); - }, - SSS : function () { - return leftZeroFill(this.milliseconds(), 3); - }, - SSSS : function () { - return leftZeroFill(this.milliseconds(), 3); - }, - Z : function () { - var a = -this.zone(), - b = '+'; - if (a < 0) { - a = -a; - b = '-'; - } - return b + leftZeroFill(toInt(a / 60), 2) + ':' + leftZeroFill(toInt(a) % 60, 2); - }, - ZZ : function () { - var a = -this.zone(), - b = '+'; - if (a < 0) { - a = -a; - b = '-'; - } - return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2); - }, - z : function () { - return this.zoneAbbr(); - }, - zz : function () { - return this.zoneName(); - }, - X : function () { - return this.unix(); - }, - Q : function () { - return this.quarter(); - } - }, - - deprecations = {}, - - lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin']; - - // Pick the first defined of two or three arguments. dfl comes from - // default. - function dfl(a, b, c) { - switch (arguments.length) { - case 2: return a != null ? a : b; - case 3: return a != null ? a : b != null ? b : c; - default: throw new Error('Implement me'); - } - } - - function defaultParsingFlags() { - // We need to deep clone this object, and es5 standard is not very - // helpful. - return { - empty : false, - unusedTokens : [], - unusedInput : [], - overflow : -2, - charsLeftOver : 0, - nullInput : false, - invalidMonth : null, - invalidFormat : false, - userInvalidated : false, - iso: false - }; - } - - function printMsg(msg) { - if (moment.suppressDeprecationWarnings === false && - typeof console !== 'undefined' && console.warn) { - console.warn("Deprecation warning: " + msg); - } - } - - function deprecate(msg, fn) { - var firstTime = true; - return extend(function () { - if (firstTime) { - printMsg(msg); - firstTime = false; - } - return fn.apply(this, arguments); - }, fn); - } - - function deprecateSimple(name, msg) { - if (!deprecations[name]) { - printMsg(msg); - deprecations[name] = true; - } - } - - function padToken(func, count) { - return function (a) { - return leftZeroFill(func.call(this, a), count); - }; - } - function ordinalizeToken(func, period) { - return function (a) { - return this.localeData().ordinal(func.call(this, a), period); - }; - } - - while (ordinalizeTokens.length) { - i = ordinalizeTokens.pop(); - formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i); - } - while (paddedTokens.length) { - i = paddedTokens.pop(); - formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2); - } - formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3); - - - /************************************ - Constructors - ************************************/ - - function Locale() { - } - - // Moment prototype object - function Moment(config, skipOverflow) { - if (skipOverflow !== false) { - checkOverflow(config); - } - copyConfig(this, config); - this._d = new Date(+config._d); - } - - // Duration Constructor - function Duration(duration) { - var normalizedInput = normalizeObjectUnits(duration), - years = normalizedInput.year || 0, - quarters = normalizedInput.quarter || 0, - months = normalizedInput.month || 0, - weeks = normalizedInput.week || 0, - days = normalizedInput.day || 0, - hours = normalizedInput.hour || 0, - minutes = normalizedInput.minute || 0, - seconds = normalizedInput.second || 0, - milliseconds = normalizedInput.millisecond || 0; - - // representation for dateAddRemove - this._milliseconds = +milliseconds + - seconds * 1e3 + // 1000 - minutes * 6e4 + // 1000 * 60 - hours * 36e5; // 1000 * 60 * 60 - // Because of dateAddRemove treats 24 hours as different from a - // day when working around DST, we need to store them separately - this._days = +days + - weeks * 7; - // It is impossible translate months into days without knowing - // which months you are are talking about, so we have to store - // it separately. - this._months = +months + - quarters * 3 + - years * 12; - - this._data = {}; - - this._locale = moment.localeData(); - - this._bubble(); - } - - /************************************ - Helpers - ************************************/ - - - function extend(a, b) { - for (var i in b) { - if (b.hasOwnProperty(i)) { - a[i] = b[i]; - } - } - - if (b.hasOwnProperty('toString')) { - a.toString = b.toString; - } - - if (b.hasOwnProperty('valueOf')) { - a.valueOf = b.valueOf; - } - - return a; - } - - function copyConfig(to, from) { - var i, prop, val; - - if (typeof from._isAMomentObject !== 'undefined') { - to._isAMomentObject = from._isAMomentObject; - } - if (typeof from._i !== 'undefined') { - to._i = from._i; - } - if (typeof from._f !== 'undefined') { - to._f = from._f; - } - if (typeof from._l !== 'undefined') { - to._l = from._l; - } - if (typeof from._strict !== 'undefined') { - to._strict = from._strict; - } - if (typeof from._tzm !== 'undefined') { - to._tzm = from._tzm; - } - if (typeof from._isUTC !== 'undefined') { - to._isUTC = from._isUTC; - } - if (typeof from._offset !== 'undefined') { - to._offset = from._offset; - } - if (typeof from._pf !== 'undefined') { - to._pf = from._pf; - } - if (typeof from._locale !== 'undefined') { - to._locale = from._locale; - } - - if (momentProperties.length > 0) { - for (i in momentProperties) { - prop = momentProperties[i]; - val = from[prop]; - if (typeof val !== 'undefined') { - to[prop] = val; - } - } - } - - return to; - } - - function absRound(number) { - if (number < 0) { - return Math.ceil(number); - } else { - return Math.floor(number); - } - } - - // left zero fill a number - // see http://jsperf.com/left-zero-filling for performance comparison - function leftZeroFill(number, targetLength, forceSign) { - var output = '' + Math.abs(number), - sign = number >= 0; - - while (output.length < targetLength) { - output = '0' + output; - } - return (sign ? (forceSign ? '+' : '') : '-') + output; - } - - function positiveMomentsDifference(base, other) { - var res = {milliseconds: 0, months: 0}; - - res.months = other.month() - base.month() + - (other.year() - base.year()) * 12; - if (base.clone().add(res.months, 'M').isAfter(other)) { - --res.months; - } - - res.milliseconds = +other - +(base.clone().add(res.months, 'M')); - - return res; - } - - function momentsDifference(base, other) { - var res; - other = makeAs(other, base); - if (base.isBefore(other)) { - res = positiveMomentsDifference(base, other); - } else { - res = positiveMomentsDifference(other, base); - res.milliseconds = -res.milliseconds; - res.months = -res.months; - } - - return res; - } - - // TODO: remove 'name' arg after deprecation is removed - function createAdder(direction, name) { - return function (val, period) { - var dur, tmp; - //invert the arguments, but complain about it - if (period !== null && !isNaN(+period)) { - deprecateSimple(name, "moment()." + name + "(period, number) is deprecated. Please use moment()." + name + "(number, period)."); - tmp = val; val = period; period = tmp; - } - - val = typeof val === 'string' ? +val : val; - dur = moment.duration(val, period); - addOrSubtractDurationFromMoment(this, dur, direction); - return this; - }; - } - - function addOrSubtractDurationFromMoment(mom, duration, isAdding, updateOffset) { - var milliseconds = duration._milliseconds, - days = duration._days, - months = duration._months; - updateOffset = updateOffset == null ? true : updateOffset; - - if (milliseconds) { - mom._d.setTime(+mom._d + milliseconds * isAdding); - } - if (days) { - rawSetter(mom, 'Date', rawGetter(mom, 'Date') + days * isAdding); - } - if (months) { - rawMonthSetter(mom, rawGetter(mom, 'Month') + months * isAdding); - } - if (updateOffset) { - moment.updateOffset(mom, days || months); - } - } - - // check if is an array - function isArray(input) { - return Object.prototype.toString.call(input) === '[object Array]'; - } - - function isDate(input) { - return Object.prototype.toString.call(input) === '[object Date]' || - input instanceof Date; - } - - // compare two arrays, return the number of differences - function compareArrays(array1, array2, dontConvert) { - var len = Math.min(array1.length, array2.length), - lengthDiff = Math.abs(array1.length - array2.length), - diffs = 0, - i; - for (i = 0; i < len; i++) { - if ((dontConvert && array1[i] !== array2[i]) || - (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) { - diffs++; - } - } - return diffs + lengthDiff; - } - - function normalizeUnits(units) { - if (units) { - var lowered = units.toLowerCase().replace(/(.)s$/, '$1'); - units = unitAliases[units] || camelFunctions[lowered] || lowered; - } - return units; - } - - function normalizeObjectUnits(inputObject) { - var normalizedInput = {}, - normalizedProp, - prop; - - for (prop in inputObject) { - if (inputObject.hasOwnProperty(prop)) { - normalizedProp = normalizeUnits(prop); - if (normalizedProp) { - normalizedInput[normalizedProp] = inputObject[prop]; - } - } - } - - return normalizedInput; - } - - function makeList(field) { - var count, setter; - - if (field.indexOf('week') === 0) { - count = 7; - setter = 'day'; - } - else if (field.indexOf('month') === 0) { - count = 12; - setter = 'month'; - } - else { - return; - } - - moment[field] = function (format, index) { - var i, getter, - method = moment._locale[field], - results = []; - - if (typeof format === 'number') { - index = format; - format = undefined; - } - - getter = function (i) { - var m = moment().utc().set(setter, i); - return method.call(moment._locale, m, format || ''); - }; - - if (index != null) { - return getter(index); - } - else { - for (i = 0; i < count; i++) { - results.push(getter(i)); - } - return results; - } - }; - } - - function toInt(argumentForCoercion) { - var coercedNumber = +argumentForCoercion, - value = 0; - - if (coercedNumber !== 0 && isFinite(coercedNumber)) { - if (coercedNumber >= 0) { - value = Math.floor(coercedNumber); - } else { - value = Math.ceil(coercedNumber); - } - } - - return value; - } - - function daysInMonth(year, month) { - return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); - } - - function weeksInYear(year, dow, doy) { - return weekOfYear(moment([year, 11, 31 + dow - doy]), dow, doy).week; - } - - function daysInYear(year) { - return isLeapYear(year) ? 366 : 365; - } - - function isLeapYear(year) { - return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; - } - - function checkOverflow(m) { - var overflow; - if (m._a && m._pf.overflow === -2) { - overflow = - m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH : - m._a[DATE] < 1 || m._a[DATE] > daysInMonth(m._a[YEAR], m._a[MONTH]) ? DATE : - m._a[HOUR] < 0 || m._a[HOUR] > 23 ? HOUR : - m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE : - m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND : - m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND : - -1; - - if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { - overflow = DATE; - } - - m._pf.overflow = overflow; - } - } - - function isValid(m) { - if (m._isValid == null) { - m._isValid = !isNaN(m._d.getTime()) && - m._pf.overflow < 0 && - !m._pf.empty && - !m._pf.invalidMonth && - !m._pf.nullInput && - !m._pf.invalidFormat && - !m._pf.userInvalidated; - - if (m._strict) { - m._isValid = m._isValid && - m._pf.charsLeftOver === 0 && - m._pf.unusedTokens.length === 0; - } - } - return m._isValid; - } - - function normalizeLocale(key) { - return key ? key.toLowerCase().replace('_', '-') : key; - } - - // pick the locale from the array - // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each - // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root - function chooseLocale(names) { - var i = 0, j, next, locale, split; - - while (i < names.length) { - split = normalizeLocale(names[i]).split('-'); - j = split.length; - next = normalizeLocale(names[i + 1]); - next = next ? next.split('-') : null; - while (j > 0) { - locale = loadLocale(split.slice(0, j).join('-')); - if (locale) { - return locale; - } - if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { - //the next array item is better than a shallower substring of this one - break; - } - j--; - } - i++; - } - return null; - } - - function loadLocale(name) { - var oldLocale = null; - if (!locales[name] && hasModule) { - try { - oldLocale = moment.locale(); - require('./locale/' + name); - // because defineLocale currently also sets the global locale, we want to undo that for lazy loaded locales - moment.locale(oldLocale); - } catch (e) { } - } - return locales[name]; - } - - // Return a moment from input, that is local/utc/zone equivalent to model. - function makeAs(input, model) { - return model._isUTC ? moment(input).zone(model._offset || 0) : - moment(input).local(); - } - - /************************************ - Locale - ************************************/ - - - extend(Locale.prototype, { - - set : function (config) { - var prop, i; - for (i in config) { - prop = config[i]; - if (typeof prop === 'function') { - this[i] = prop; - } else { - this['_' + i] = prop; - } - } - }, - - _months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'), - months : function (m) { - return this._months[m.month()]; - }, - - _monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'), - monthsShort : function (m) { - return this._monthsShort[m.month()]; - }, - - monthsParse : function (monthName) { - var i, mom, regex; - - if (!this._monthsParse) { - this._monthsParse = []; - } - - for (i = 0; i < 12; i++) { - // make the regex if we don't have it already - if (!this._monthsParse[i]) { - mom = moment.utc([2000, i]); - regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); - this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); - } - // test the regex - if (this._monthsParse[i].test(monthName)) { - return i; - } - } - }, - - _weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), - weekdays : function (m) { - return this._weekdays[m.day()]; - }, - - _weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), - weekdaysShort : function (m) { - return this._weekdaysShort[m.day()]; - }, - - _weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), - weekdaysMin : function (m) { - return this._weekdaysMin[m.day()]; - }, - - weekdaysParse : function (weekdayName) { - var i, mom, regex; - - if (!this._weekdaysParse) { - this._weekdaysParse = []; - } - - for (i = 0; i < 7; i++) { - // make the regex if we don't have it already - if (!this._weekdaysParse[i]) { - mom = moment([2000, 1]).day(i); - regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); - this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); - } - // test the regex - if (this._weekdaysParse[i].test(weekdayName)) { - return i; - } - } - }, - - _longDateFormat : { - LT : 'h:mm A', - L : 'MM/DD/YYYY', - LL : 'MMMM D, YYYY', - LLL : 'MMMM D, YYYY LT', - LLLL : 'dddd, MMMM D, YYYY LT' - }, - longDateFormat : function (key) { - var output = this._longDateFormat[key]; - if (!output && this._longDateFormat[key.toUpperCase()]) { - output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) { - return val.slice(1); - }); - this._longDateFormat[key] = output; - } - return output; - }, - - isPM : function (input) { - // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays - // Using charAt should be more compatible. - return ((input + '').toLowerCase().charAt(0) === 'p'); - }, - - _meridiemParse : /[ap]\.?m?\.?/i, - meridiem : function (hours, minutes, isLower) { - if (hours > 11) { - return isLower ? 'pm' : 'PM'; - } else { - return isLower ? 'am' : 'AM'; - } - }, - - _calendar : { - sameDay : '[Today at] LT', - nextDay : '[Tomorrow at] LT', - nextWeek : 'dddd [at] LT', - lastDay : '[Yesterday at] LT', - lastWeek : '[Last] dddd [at] LT', - sameElse : 'L' - }, - calendar : function (key, mom) { - var output = this._calendar[key]; - return typeof output === 'function' ? output.apply(mom) : output; - }, - - _relativeTime : { - future : 'in %s', - past : '%s ago', - s : 'a few seconds', - m : 'a minute', - mm : '%d minutes', - h : 'an hour', - hh : '%d hours', - d : 'a day', - dd : '%d days', - M : 'a month', - MM : '%d months', - y : 'a year', - yy : '%d years' - }, - - relativeTime : function (number, withoutSuffix, string, isFuture) { - var output = this._relativeTime[string]; - return (typeof output === 'function') ? - output(number, withoutSuffix, string, isFuture) : - output.replace(/%d/i, number); - }, - - pastFuture : function (diff, output) { - var format = this._relativeTime[diff > 0 ? 'future' : 'past']; - return typeof format === 'function' ? format(output) : format.replace(/%s/i, output); - }, - - ordinal : function (number) { - return this._ordinal.replace('%d', number); - }, - _ordinal : '%d', - - preparse : function (string) { - return string; - }, - - postformat : function (string) { - return string; - }, - - week : function (mom) { - return weekOfYear(mom, this._week.dow, this._week.doy).week; - }, - - _week : { - dow : 0, // Sunday is the first day of the week. - doy : 6 // The week that contains Jan 1st is the first week of the year. - }, - - _invalidDate: 'Invalid date', - invalidDate: function () { - return this._invalidDate; - } - }); - - /************************************ - Formatting - ************************************/ - - - function removeFormattingTokens(input) { - if (input.match(/\[[\s\S]/)) { - return input.replace(/^\[|\]$/g, ''); - } - return input.replace(/\\/g, ''); - } - - function makeFormatFunction(format) { - var array = format.match(formattingTokens), i, length; - - for (i = 0, length = array.length; i < length; i++) { - if (formatTokenFunctions[array[i]]) { - array[i] = formatTokenFunctions[array[i]]; - } else { - array[i] = removeFormattingTokens(array[i]); - } - } - - return function (mom) { - var output = ''; - for (i = 0; i < length; i++) { - output += array[i] instanceof Function ? array[i].call(mom, format) : array[i]; - } - return output; - }; - } - - // format date using native date object - function formatMoment(m, format) { - if (!m.isValid()) { - return m.localeData().invalidDate(); - } - - format = expandFormat(format, m.localeData()); - - if (!formatFunctions[format]) { - formatFunctions[format] = makeFormatFunction(format); - } - - return formatFunctions[format](m); - } - - function expandFormat(format, locale) { - var i = 5; - - function replaceLongDateFormatTokens(input) { - return locale.longDateFormat(input) || input; - } - - localFormattingTokens.lastIndex = 0; - while (i >= 0 && localFormattingTokens.test(format)) { - format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); - localFormattingTokens.lastIndex = 0; - i -= 1; - } - - return format; - } - - - /************************************ - Parsing - ************************************/ - - - // get the regex to find the next token - function getParseRegexForToken(token, config) { - var a, strict = config._strict; - switch (token) { - case 'Q': - return parseTokenOneDigit; - case 'DDDD': - return parseTokenThreeDigits; - case 'YYYY': - case 'GGGG': - case 'gggg': - return strict ? parseTokenFourDigits : parseTokenOneToFourDigits; - case 'Y': - case 'G': - case 'g': - return parseTokenSignedNumber; - case 'YYYYYY': - case 'YYYYY': - case 'GGGGG': - case 'ggggg': - return strict ? parseTokenSixDigits : parseTokenOneToSixDigits; - case 'S': - if (strict) { - return parseTokenOneDigit; - } - /* falls through */ - case 'SS': - if (strict) { - return parseTokenTwoDigits; - } - /* falls through */ - case 'SSS': - if (strict) { - return parseTokenThreeDigits; - } - /* falls through */ - case 'DDD': - return parseTokenOneToThreeDigits; - case 'MMM': - case 'MMMM': - case 'dd': - case 'ddd': - case 'dddd': - return parseTokenWord; - case 'a': - case 'A': - return config._locale._meridiemParse; - case 'X': - return parseTokenTimestampMs; - case 'Z': - case 'ZZ': - return parseTokenTimezone; - case 'T': - return parseTokenT; - case 'SSSS': - return parseTokenDigits; - case 'MM': - case 'DD': - case 'YY': - case 'GG': - case 'gg': - case 'HH': - case 'hh': - case 'mm': - case 'ss': - case 'ww': - case 'WW': - return strict ? parseTokenTwoDigits : parseTokenOneOrTwoDigits; - case 'M': - case 'D': - case 'd': - case 'H': - case 'h': - case 'm': - case 's': - case 'w': - case 'W': - case 'e': - case 'E': - return parseTokenOneOrTwoDigits; - case 'Do': - return parseTokenOrdinal; - default : - a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), 'i')); - return a; - } - } - - function timezoneMinutesFromString(string) { - string = string || ''; - var possibleTzMatches = (string.match(parseTokenTimezone) || []), - tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [], - parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0], - minutes = +(parts[1] * 60) + toInt(parts[2]); - - return parts[0] === '+' ? -minutes : minutes; - } - - // function to convert string input to date - function addTimeToArrayFromToken(token, input, config) { - var a, datePartArray = config._a; - - switch (token) { - // QUARTER - case 'Q': - if (input != null) { - datePartArray[MONTH] = (toInt(input) - 1) * 3; - } - break; - // MONTH - case 'M' : // fall through to MM - case 'MM' : - if (input != null) { - datePartArray[MONTH] = toInt(input) - 1; - } - break; - case 'MMM' : // fall through to MMMM - case 'MMMM' : - a = config._locale.monthsParse(input); - // if we didn't find a month name, mark the date as invalid. - if (a != null) { - datePartArray[MONTH] = a; - } else { - config._pf.invalidMonth = input; - } - break; - // DAY OF MONTH - case 'D' : // fall through to DD - case 'DD' : - if (input != null) { - datePartArray[DATE] = toInt(input); - } - break; - case 'Do' : - if (input != null) { - datePartArray[DATE] = toInt(parseInt(input, 10)); - } - break; - // DAY OF YEAR - case 'DDD' : // fall through to DDDD - case 'DDDD' : - if (input != null) { - config._dayOfYear = toInt(input); - } - - break; - // YEAR - case 'YY' : - datePartArray[YEAR] = moment.parseTwoDigitYear(input); - break; - case 'YYYY' : - case 'YYYYY' : - case 'YYYYYY' : - datePartArray[YEAR] = toInt(input); - break; - // AM / PM - case 'a' : // fall through to A - case 'A' : - config._isPm = config._locale.isPM(input); - break; - // 24 HOUR - case 'H' : // fall through to hh - case 'HH' : // fall through to hh - case 'h' : // fall through to hh - case 'hh' : - datePartArray[HOUR] = toInt(input); - break; - // MINUTE - case 'm' : // fall through to mm - case 'mm' : - datePartArray[MINUTE] = toInt(input); - break; - // SECOND - case 's' : // fall through to ss - case 'ss' : - datePartArray[SECOND] = toInt(input); - break; - // MILLISECOND - case 'S' : - case 'SS' : - case 'SSS' : - case 'SSSS' : - datePartArray[MILLISECOND] = toInt(('0.' + input) * 1000); - break; - // UNIX TIMESTAMP WITH MS - case 'X': - config._d = new Date(parseFloat(input) * 1000); - break; - // TIMEZONE - case 'Z' : // fall through to ZZ - case 'ZZ' : - config._useUTC = true; - config._tzm = timezoneMinutesFromString(input); - break; - // WEEKDAY - human - case 'dd': - case 'ddd': - case 'dddd': - a = config._locale.weekdaysParse(input); - // if we didn't get a weekday name, mark the date as invalid - if (a != null) { - config._w = config._w || {}; - config._w['d'] = a; - } else { - config._pf.invalidWeekday = input; - } - break; - // WEEK, WEEK DAY - numeric - case 'w': - case 'ww': - case 'W': - case 'WW': - case 'd': - case 'e': - case 'E': - token = token.substr(0, 1); - /* falls through */ - case 'gggg': - case 'GGGG': - case 'GGGGG': - token = token.substr(0, 2); - if (input) { - config._w = config._w || {}; - config._w[token] = toInt(input); - } - break; - case 'gg': - case 'GG': - config._w = config._w || {}; - config._w[token] = moment.parseTwoDigitYear(input); - } - } - - function dayOfYearFromWeekInfo(config) { - var w, weekYear, week, weekday, dow, doy, temp; - - w = config._w; - if (w.GG != null || w.W != null || w.E != null) { - dow = 1; - doy = 4; - - // TODO: We need to take the current isoWeekYear, but that depends on - // how we interpret now (local, utc, fixed offset). So create - // a now version of current config (take local/utc/offset flags, and - // create now). - weekYear = dfl(w.GG, config._a[YEAR], weekOfYear(moment(), 1, 4).year); - week = dfl(w.W, 1); - weekday = dfl(w.E, 1); - } else { - dow = config._locale._week.dow; - doy = config._locale._week.doy; - - weekYear = dfl(w.gg, config._a[YEAR], weekOfYear(moment(), dow, doy).year); - week = dfl(w.w, 1); - - if (w.d != null) { - // weekday -- low day numbers are considered next week - weekday = w.d; - if (weekday < dow) { - ++week; - } - } else if (w.e != null) { - // local weekday -- counting starts from begining of week - weekday = w.e + dow; - } else { - // default to begining of week - weekday = dow; - } - } - temp = dayOfYearFromWeeks(weekYear, week, weekday, doy, dow); - - config._a[YEAR] = temp.year; - config._dayOfYear = temp.dayOfYear; - } - - // convert an array to a date. - // the array should mirror the parameters below - // note: all values past the year are optional and will default to the lowest possible value. - // [year, month, day , hour, minute, second, millisecond] - function dateFromConfig(config) { - var i, date, input = [], currentDate, yearToUse; - - if (config._d) { - return; - } - - currentDate = currentDateArray(config); - - //compute day of the year from weeks and weekdays - if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { - dayOfYearFromWeekInfo(config); - } - - //if the day of the year is set, figure out what it is - if (config._dayOfYear) { - yearToUse = dfl(config._a[YEAR], currentDate[YEAR]); - - if (config._dayOfYear > daysInYear(yearToUse)) { - config._pf._overflowDayOfYear = true; - } - - date = makeUTCDate(yearToUse, 0, config._dayOfYear); - config._a[MONTH] = date.getUTCMonth(); - config._a[DATE] = date.getUTCDate(); - } - - // Default to current date. - // * if no year, month, day of month are given, default to today - // * if day of month is given, default month and year - // * if month is given, default only year - // * if year is given, don't default anything - for (i = 0; i < 3 && config._a[i] == null; ++i) { - config._a[i] = input[i] = currentDate[i]; - } - - // Zero out whatever was not defaulted, including time - for (; i < 7; i++) { - config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; - } - - config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input); - // Apply timezone offset from input. The actual zone can be changed - // with parseZone. - if (config._tzm != null) { - config._d.setUTCMinutes(config._d.getUTCMinutes() + config._tzm); - } - } - - function dateFromObject(config) { - var normalizedInput; - - if (config._d) { - return; - } - - normalizedInput = normalizeObjectUnits(config._i); - config._a = [ - normalizedInput.year, - normalizedInput.month, - normalizedInput.day, - normalizedInput.hour, - normalizedInput.minute, - normalizedInput.second, - normalizedInput.millisecond - ]; - - dateFromConfig(config); - } - - function currentDateArray(config) { - var now = new Date(); - if (config._useUTC) { - return [ - now.getUTCFullYear(), - now.getUTCMonth(), - now.getUTCDate() - ]; - } else { - return [now.getFullYear(), now.getMonth(), now.getDate()]; - } - } - - // date from string and format string - function makeDateFromStringAndFormat(config) { - if (config._f === moment.ISO_8601) { - parseISO(config); - return; - } - - config._a = []; - config._pf.empty = true; - - // This array is used to make a Date, either with `new Date` or `Date.UTC` - var string = '' + config._i, - i, parsedInput, tokens, token, skipped, - stringLength = string.length, - totalParsedInputLength = 0; - - tokens = expandFormat(config._f, config._locale).match(formattingTokens) || []; - - for (i = 0; i < tokens.length; i++) { - token = tokens[i]; - parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; - if (parsedInput) { - skipped = string.substr(0, string.indexOf(parsedInput)); - if (skipped.length > 0) { - config._pf.unusedInput.push(skipped); - } - string = string.slice(string.indexOf(parsedInput) + parsedInput.length); - totalParsedInputLength += parsedInput.length; - } - // don't parse if it's not a known token - if (formatTokenFunctions[token]) { - if (parsedInput) { - config._pf.empty = false; - } - else { - config._pf.unusedTokens.push(token); - } - addTimeToArrayFromToken(token, parsedInput, config); - } - else if (config._strict && !parsedInput) { - config._pf.unusedTokens.push(token); - } - } - - // add remaining unparsed input length to the string - config._pf.charsLeftOver = stringLength - totalParsedInputLength; - if (string.length > 0) { - config._pf.unusedInput.push(string); - } - - // handle am pm - if (config._isPm && config._a[HOUR] < 12) { - config._a[HOUR] += 12; - } - // if is 12 am, change hours to 0 - if (config._isPm === false && config._a[HOUR] === 12) { - config._a[HOUR] = 0; - } - - dateFromConfig(config); - checkOverflow(config); - } - - function unescapeFormat(s) { - return s.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { - return p1 || p2 || p3 || p4; - }); - } - - // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript - function regexpEscape(s) { - return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); - } - - // date from string and array of format strings - function makeDateFromStringAndArray(config) { - var tempConfig, - bestMoment, - - scoreToBeat, - i, - currentScore; - - if (config._f.length === 0) { - config._pf.invalidFormat = true; - config._d = new Date(NaN); - return; - } - - for (i = 0; i < config._f.length; i++) { - currentScore = 0; - tempConfig = copyConfig({}, config); - tempConfig._pf = defaultParsingFlags(); - tempConfig._f = config._f[i]; - makeDateFromStringAndFormat(tempConfig); - - if (!isValid(tempConfig)) { - continue; - } - - // if there is any input that was not parsed add a penalty for that format - currentScore += tempConfig._pf.charsLeftOver; - - //or tokens - currentScore += tempConfig._pf.unusedTokens.length * 10; - - tempConfig._pf.score = currentScore; - - if (scoreToBeat == null || currentScore < scoreToBeat) { - scoreToBeat = currentScore; - bestMoment = tempConfig; - } - } - - extend(config, bestMoment || tempConfig); - } - - // date from iso format - function parseISO(config) { - var i, l, - string = config._i, - match = isoRegex.exec(string); - - if (match) { - config._pf.iso = true; - for (i = 0, l = isoDates.length; i < l; i++) { - if (isoDates[i][1].exec(string)) { - // match[5] should be "T" or undefined - config._f = isoDates[i][0] + (match[6] || ' '); - break; - } - } - for (i = 0, l = isoTimes.length; i < l; i++) { - if (isoTimes[i][1].exec(string)) { - config._f += isoTimes[i][0]; - break; - } - } - if (string.match(parseTokenTimezone)) { - config._f += 'Z'; - } - makeDateFromStringAndFormat(config); - } else { - config._isValid = false; - } - } - - // date from iso format or fallback - function makeDateFromString(config) { - parseISO(config); - if (config._isValid === false) { - delete config._isValid; - moment.createFromInputFallback(config); - } - } - - function makeDateFromInput(config) { - var input = config._i, matched; - if (input === undefined) { - config._d = new Date(); - } else if (isDate(input)) { - config._d = new Date(+input); - } else if ((matched = aspNetJsonRegex.exec(input)) !== null) { - config._d = new Date(+matched[1]); - } else if (typeof input === 'string') { - makeDateFromString(config); - } else if (isArray(input)) { - config._a = input.slice(0); - dateFromConfig(config); - } else if (typeof(input) === 'object') { - dateFromObject(config); - } else if (typeof(input) === 'number') { - // from milliseconds - config._d = new Date(input); - } else { - moment.createFromInputFallback(config); - } - } - - function makeDate(y, m, d, h, M, s, ms) { - //can't just apply() to create a date: - //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply - var date = new Date(y, m, d, h, M, s, ms); - - //the date constructor doesn't accept years < 1970 - if (y < 1970) { - date.setFullYear(y); - } - return date; - } - - function makeUTCDate(y) { - var date = new Date(Date.UTC.apply(null, arguments)); - if (y < 1970) { - date.setUTCFullYear(y); - } - return date; - } - - function parseWeekday(input, locale) { - if (typeof input === 'string') { - if (!isNaN(input)) { - input = parseInt(input, 10); - } - else { - input = locale.weekdaysParse(input); - if (typeof input !== 'number') { - return null; - } - } - } - return input; - } - - /************************************ - Relative Time - ************************************/ - - - // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize - function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { - return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); - } - - function relativeTime(posNegDuration, withoutSuffix, locale) { - var duration = moment.duration(posNegDuration).abs(), - seconds = round(duration.as('s')), - minutes = round(duration.as('m')), - hours = round(duration.as('h')), - days = round(duration.as('d')), - months = round(duration.as('M')), - years = round(duration.as('y')), - - args = seconds < relativeTimeThresholds.s && ['s', seconds] || - minutes === 1 && ['m'] || - minutes < relativeTimeThresholds.m && ['mm', minutes] || - hours === 1 && ['h'] || - hours < relativeTimeThresholds.h && ['hh', hours] || - days === 1 && ['d'] || - days < relativeTimeThresholds.d && ['dd', days] || - months === 1 && ['M'] || - months < relativeTimeThresholds.M && ['MM', months] || - years === 1 && ['y'] || ['yy', years]; - - args[2] = withoutSuffix; - args[3] = +posNegDuration > 0; - args[4] = locale; - return substituteTimeAgo.apply({}, args); - } - - - /************************************ - Week of Year - ************************************/ - - - // firstDayOfWeek 0 = sun, 6 = sat - // the day of the week that starts the week - // (usually sunday or monday) - // firstDayOfWeekOfYear 0 = sun, 6 = sat - // the first week is the week that contains the first - // of this day of the week - // (eg. ISO weeks use thursday (4)) - function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) { - var end = firstDayOfWeekOfYear - firstDayOfWeek, - daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(), - adjustedMoment; - - - if (daysToDayOfWeek > end) { - daysToDayOfWeek -= 7; - } - - if (daysToDayOfWeek < end - 7) { - daysToDayOfWeek += 7; - } - - adjustedMoment = moment(mom).add(daysToDayOfWeek, 'd'); - return { - week: Math.ceil(adjustedMoment.dayOfYear() / 7), - year: adjustedMoment.year() - }; - } - - //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday - function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) { - var d = makeUTCDate(year, 0, 1).getUTCDay(), daysToAdd, dayOfYear; - - d = d === 0 ? 7 : d; - weekday = weekday != null ? weekday : firstDayOfWeek; - daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0); - dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1; - - return { - year: dayOfYear > 0 ? year : year - 1, - dayOfYear: dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear - }; - } - - /************************************ - Top Level Functions - ************************************/ - - function makeMoment(config) { - var input = config._i, - format = config._f; - - config._locale = config._locale || moment.localeData(config._l); - - if (input === null || (format === undefined && input === '')) { - return moment.invalid({nullInput: true}); - } - - if (typeof input === 'string') { - config._i = input = config._locale.preparse(input); - } - - if (moment.isMoment(input)) { - return new Moment(input, true); - } else if (format) { - if (isArray(format)) { - makeDateFromStringAndArray(config); - } else { - makeDateFromStringAndFormat(config); - } - } else { - makeDateFromInput(config); - } - - return new Moment(config); - } - - moment = function (input, format, locale, strict) { - var c; - - if (typeof(locale) === "boolean") { - strict = locale; - locale = undefined; - } - // object construction must be done this way. - // https://github.com/moment/moment/issues/1423 - c = {}; - c._isAMomentObject = true; - c._i = input; - c._f = format; - c._l = locale; - c._strict = strict; - c._isUTC = false; - c._pf = defaultParsingFlags(); - - return makeMoment(c); - }; - - moment.suppressDeprecationWarnings = false; - - moment.createFromInputFallback = deprecate( - 'moment construction falls back to js Date. This is ' + - 'discouraged and will be removed in upcoming major ' + - 'release. Please refer to ' + - 'https://github.com/moment/moment/issues/1407 for more info.', - function (config) { - config._d = new Date(config._i); - } - ); - - // Pick a moment m from moments so that m[fn](other) is true for all - // other. This relies on the function fn to be transitive. - // - // moments should either be an array of moment objects or an array, whose - // first element is an array of moment objects. - function pickBy(fn, moments) { - var res, i; - if (moments.length === 1 && isArray(moments[0])) { - moments = moments[0]; - } - if (!moments.length) { - return moment(); - } - res = moments[0]; - for (i = 1; i < moments.length; ++i) { - if (moments[i][fn](res)) { - res = moments[i]; - } - } - return res; - } - - moment.min = function () { - var args = [].slice.call(arguments, 0); - - return pickBy('isBefore', args); - }; - - moment.max = function () { - var args = [].slice.call(arguments, 0); - - return pickBy('isAfter', args); - }; - - // creating with utc - moment.utc = function (input, format, locale, strict) { - var c; - - if (typeof(locale) === "boolean") { - strict = locale; - locale = undefined; - } - // object construction must be done this way. - // https://github.com/moment/moment/issues/1423 - c = {}; - c._isAMomentObject = true; - c._useUTC = true; - c._isUTC = true; - c._l = locale; - c._i = input; - c._f = format; - c._strict = strict; - c._pf = defaultParsingFlags(); - - return makeMoment(c).utc(); - }; - - // creating with unix timestamp (in seconds) - moment.unix = function (input) { - return moment(input * 1000); - }; - - // duration - moment.duration = function (input, key) { - var duration = input, - // matching against regexp is expensive, do it on demand - match = null, - sign, - ret, - parseIso, - diffRes; - - if (moment.isDuration(input)) { - duration = { - ms: input._milliseconds, - d: input._days, - M: input._months - }; - } else if (typeof input === 'number') { - duration = {}; - if (key) { - duration[key] = input; - } else { - duration.milliseconds = input; - } - } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) { - sign = (match[1] === '-') ? -1 : 1; - duration = { - y: 0, - d: toInt(match[DATE]) * sign, - h: toInt(match[HOUR]) * sign, - m: toInt(match[MINUTE]) * sign, - s: toInt(match[SECOND]) * sign, - ms: toInt(match[MILLISECOND]) * sign - }; - } else if (!!(match = isoDurationRegex.exec(input))) { - sign = (match[1] === '-') ? -1 : 1; - parseIso = function (inp) { - // We'd normally use ~~inp for this, but unfortunately it also - // converts floats to ints. - // inp may be undefined, so careful calling replace on it. - var res = inp && parseFloat(inp.replace(',', '.')); - // apply sign while we're at it - return (isNaN(res) ? 0 : res) * sign; - }; - duration = { - y: parseIso(match[2]), - M: parseIso(match[3]), - d: parseIso(match[4]), - h: parseIso(match[5]), - m: parseIso(match[6]), - s: parseIso(match[7]), - w: parseIso(match[8]) - }; - } else if (typeof duration === 'object' && - ('from' in duration || 'to' in duration)) { - diffRes = momentsDifference(moment(duration.from), moment(duration.to)); - - duration = {}; - duration.ms = diffRes.milliseconds; - duration.M = diffRes.months; - } - - ret = new Duration(duration); - - if (moment.isDuration(input) && input.hasOwnProperty('_locale')) { - ret._locale = input._locale; - } - - return ret; - }; - - // version number - moment.version = VERSION; - - // default format - moment.defaultFormat = isoFormat; - - // constant that refers to the ISO standard - moment.ISO_8601 = function () {}; - - // Plugins that add properties should also add the key here (null value), - // so we can properly clone ourselves. - moment.momentProperties = momentProperties; - - // This function will be called whenever a moment is mutated. - // It is intended to keep the offset in sync with the timezone. - moment.updateOffset = function () {}; - - // This function allows you to set a threshold for relative time strings - moment.relativeTimeThreshold = function (threshold, limit) { - if (relativeTimeThresholds[threshold] === undefined) { - return false; - } - if (limit === undefined) { - return relativeTimeThresholds[threshold]; - } - relativeTimeThresholds[threshold] = limit; - return true; - }; - - moment.lang = deprecate( - "moment.lang is deprecated. Use moment.locale instead.", - function (key, value) { - return moment.locale(key, value); - } - ); - - // This function will load locale and then set the global locale. If - // no arguments are passed in, it will simply return the current global - // locale key. - moment.locale = function (key, values) { - var data; - if (key) { - if (typeof(values) !== "undefined") { - data = moment.defineLocale(key, values); - } - else { - data = moment.localeData(key); - } - - if (data) { - moment.duration._locale = moment._locale = data; - } - } - - return moment._locale._abbr; - }; - - moment.defineLocale = function (name, values) { - if (values !== null) { - values.abbr = name; - if (!locales[name]) { - locales[name] = new Locale(); - } - locales[name].set(values); - - // backwards compat for now: also set the locale - moment.locale(name); - - return locales[name]; - } else { - // useful for testing - delete locales[name]; - return null; - } - }; - - moment.langData = deprecate( - "moment.langData is deprecated. Use moment.localeData instead.", - function (key) { - return moment.localeData(key); - } - ); - - // returns locale data - moment.localeData = function (key) { - var locale; - - if (key && key._locale && key._locale._abbr) { - key = key._locale._abbr; - } - - if (!key) { - return moment._locale; - } - - if (!isArray(key)) { - //short-circuit everything else - locale = loadLocale(key); - if (locale) { - return locale; - } - key = [key]; - } - - return chooseLocale(key); - }; - - // compare moment object - moment.isMoment = function (obj) { - return obj instanceof Moment || - (obj != null && obj.hasOwnProperty('_isAMomentObject')); - }; - - // for typechecking Duration objects - moment.isDuration = function (obj) { - return obj instanceof Duration; - }; - - for (i = lists.length - 1; i >= 0; --i) { - makeList(lists[i]); - } - - moment.normalizeUnits = function (units) { - return normalizeUnits(units); - }; - - moment.invalid = function (flags) { - var m = moment.utc(NaN); - if (flags != null) { - extend(m._pf, flags); - } - else { - m._pf.userInvalidated = true; - } - - return m; - }; - - moment.parseZone = function () { - return moment.apply(null, arguments).parseZone(); - }; - - moment.parseTwoDigitYear = function (input) { - return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); - }; - - /************************************ - Moment Prototype - ************************************/ - - - extend(moment.fn = Moment.prototype, { - - clone : function () { - return moment(this); - }, - - valueOf : function () { - return +this._d + ((this._offset || 0) * 60000); - }, - - unix : function () { - return Math.floor(+this / 1000); - }, - - toString : function () { - return this.clone().locale('en').format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ"); - }, - - toDate : function () { - return this._offset ? new Date(+this) : this._d; - }, - - toISOString : function () { - var m = moment(this).utc(); - if (0 < m.year() && m.year() <= 9999) { - return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); - } else { - return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); - } - }, - - toArray : function () { - var m = this; - return [ - m.year(), - m.month(), - m.date(), - m.hours(), - m.minutes(), - m.seconds(), - m.milliseconds() - ]; - }, - - isValid : function () { - return isValid(this); - }, - - isDSTShifted : function () { - if (this._a) { - return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0; - } - - return false; - }, - - parsingFlags : function () { - return extend({}, this._pf); - }, - - invalidAt: function () { - return this._pf.overflow; - }, - - utc : function (keepLocalTime) { - return this.zone(0, keepLocalTime); - }, - - local : function (keepLocalTime) { - if (this._isUTC) { - this.zone(0, keepLocalTime); - this._isUTC = false; - - if (keepLocalTime) { - this.add(this._d.getTimezoneOffset(), 'm'); - } - } - return this; - }, - - format : function (inputString) { - var output = formatMoment(this, inputString || moment.defaultFormat); - return this.localeData().postformat(output); - }, - - add : createAdder(1, 'add'), - - subtract : createAdder(-1, 'subtract'), - - diff : function (input, units, asFloat) { - var that = makeAs(input, this), - zoneDiff = (this.zone() - that.zone()) * 6e4, - diff, output; - - units = normalizeUnits(units); - - if (units === 'year' || units === 'month') { - // average number of days in the months in the given dates - diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2 - // difference in months - output = ((this.year() - that.year()) * 12) + (this.month() - that.month()); - // adjust by taking difference in days, average number of days - // and dst in the given months. - output += ((this - moment(this).startOf('month')) - - (that - moment(that).startOf('month'))) / diff; - // same as above but with zones, to negate all dst - output -= ((this.zone() - moment(this).startOf('month').zone()) - - (that.zone() - moment(that).startOf('month').zone())) * 6e4 / diff; - if (units === 'year') { - output = output / 12; - } - } else { - diff = (this - that); - output = units === 'second' ? diff / 1e3 : // 1000 - units === 'minute' ? diff / 6e4 : // 1000 * 60 - units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60 - units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst - units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst - diff; - } - return asFloat ? output : absRound(output); - }, - - from : function (time, withoutSuffix) { - return moment.duration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix); - }, - - fromNow : function (withoutSuffix) { - return this.from(moment(), withoutSuffix); - }, - - calendar : function (time) { - // We want to compare the start of today, vs this. - // Getting start-of-today depends on whether we're zone'd or not. - var now = time || moment(), - sod = makeAs(now, this).startOf('day'), - diff = this.diff(sod, 'days', true), - format = diff < -6 ? 'sameElse' : - diff < -1 ? 'lastWeek' : - diff < 0 ? 'lastDay' : - diff < 1 ? 'sameDay' : - diff < 2 ? 'nextDay' : - diff < 7 ? 'nextWeek' : 'sameElse'; - return this.format(this.localeData().calendar(format, this)); - }, - - isLeapYear : function () { - return isLeapYear(this.year()); - }, - - isDST : function () { - return (this.zone() < this.clone().month(0).zone() || - this.zone() < this.clone().month(5).zone()); - }, - - day : function (input) { - var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); - if (input != null) { - input = parseWeekday(input, this.localeData()); - return this.add(input - day, 'd'); - } else { - return day; - } - }, - - month : makeAccessor('Month', true), - - startOf : function (units) { - units = normalizeUnits(units); - // the following switch intentionally omits break keywords - // to utilize falling through the cases. - switch (units) { - case 'year': - this.month(0); - /* falls through */ - case 'quarter': - case 'month': - this.date(1); - /* falls through */ - case 'week': - case 'isoWeek': - case 'day': - this.hours(0); - /* falls through */ - case 'hour': - this.minutes(0); - /* falls through */ - case 'minute': - this.seconds(0); - /* falls through */ - case 'second': - this.milliseconds(0); - /* falls through */ - } - - // weeks are a special case - if (units === 'week') { - this.weekday(0); - } else if (units === 'isoWeek') { - this.isoWeekday(1); - } - - // quarters are also special - if (units === 'quarter') { - this.month(Math.floor(this.month() / 3) * 3); - } - - return this; - }, - - endOf: function (units) { - units = normalizeUnits(units); - return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms'); - }, - - isAfter: function (input, units) { - units = typeof units !== 'undefined' ? units : 'millisecond'; - return +this.clone().startOf(units) > +moment(input).startOf(units); - }, - - isBefore: function (input, units) { - units = typeof units !== 'undefined' ? units : 'millisecond'; - return +this.clone().startOf(units) < +moment(input).startOf(units); - }, - - isSame: function (input, units) { - units = units || 'ms'; - return +this.clone().startOf(units) === +makeAs(input, this).startOf(units); - }, - - min: deprecate( - 'moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548', - function (other) { - other = moment.apply(null, arguments); - return other < this ? this : other; - } - ), - - max: deprecate( - 'moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548', - function (other) { - other = moment.apply(null, arguments); - return other > this ? this : other; - } - ), - - // keepLocalTime = true means only change the timezone, without - // affecting the local hour. So 5:31:26 +0300 --[zone(2, true)]--> - // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist int zone - // +0200, so we adjust the time as needed, to be valid. - // - // Keeping the time actually adds/subtracts (one hour) - // from the actual represented time. That is why we call updateOffset - // a second time. In case it wants us to change the offset again - // _changeInProgress == true case, then we have to adjust, because - // there is no such time in the given timezone. - zone : function (input, keepLocalTime) { - var offset = this._offset || 0, - localAdjust; - if (input != null) { - if (typeof input === 'string') { - input = timezoneMinutesFromString(input); - } - if (Math.abs(input) < 16) { - input = input * 60; - } - if (!this._isUTC && keepLocalTime) { - localAdjust = this._d.getTimezoneOffset(); - } - this._offset = input; - this._isUTC = true; - if (localAdjust != null) { - this.subtract(localAdjust, 'm'); - } - if (offset !== input) { - if (!keepLocalTime || this._changeInProgress) { - addOrSubtractDurationFromMoment(this, - moment.duration(offset - input, 'm'), 1, false); - } else if (!this._changeInProgress) { - this._changeInProgress = true; - moment.updateOffset(this, true); - this._changeInProgress = null; - } - } - } else { - return this._isUTC ? offset : this._d.getTimezoneOffset(); - } - return this; - }, - - zoneAbbr : function () { - return this._isUTC ? 'UTC' : ''; - }, - - zoneName : function () { - return this._isUTC ? 'Coordinated Universal Time' : ''; - }, - - parseZone : function () { - if (this._tzm) { - this.zone(this._tzm); - } else if (typeof this._i === 'string') { - this.zone(this._i); - } - return this; - }, - - hasAlignedHourOffset : function (input) { - if (!input) { - input = 0; - } - else { - input = moment(input).zone(); - } - - return (this.zone() - input) % 60 === 0; - }, - - daysInMonth : function () { - return daysInMonth(this.year(), this.month()); - }, - - dayOfYear : function (input) { - var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1; - return input == null ? dayOfYear : this.add((input - dayOfYear), 'd'); - }, - - quarter : function (input) { - return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); - }, - - weekYear : function (input) { - var year = weekOfYear(this, this.localeData()._week.dow, this.localeData()._week.doy).year; - return input == null ? year : this.add((input - year), 'y'); - }, - - isoWeekYear : function (input) { - var year = weekOfYear(this, 1, 4).year; - return input == null ? year : this.add((input - year), 'y'); - }, - - week : function (input) { - var week = this.localeData().week(this); - return input == null ? week : this.add((input - week) * 7, 'd'); - }, - - isoWeek : function (input) { - var week = weekOfYear(this, 1, 4).week; - return input == null ? week : this.add((input - week) * 7, 'd'); - }, - - weekday : function (input) { - var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; - return input == null ? weekday : this.add(input - weekday, 'd'); - }, - - isoWeekday : function (input) { - // behaves the same as moment#day except - // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) - // as a setter, sunday should belong to the previous week. - return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7); - }, - - isoWeeksInYear : function () { - return weeksInYear(this.year(), 1, 4); - }, - - weeksInYear : function () { - var weekInfo = this.localeData()._week; - return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); - }, - - get : function (units) { - units = normalizeUnits(units); - return this[units](); - }, - - set : function (units, value) { - units = normalizeUnits(units); - if (typeof this[units] === 'function') { - this[units](value); - } - return this; - }, - - // If passed a locale key, it will set the locale for this - // instance. Otherwise, it will return the locale configuration - // variables for this instance. - locale : function (key) { - if (key === undefined) { - return this._locale._abbr; - } else { - this._locale = moment.localeData(key); - return this; - } - }, - - lang : deprecate( - "moment().lang() is deprecated. Use moment().localeData() instead.", - function (key) { - if (key === undefined) { - return this.localeData(); - } else { - this._locale = moment.localeData(key); - return this; - } - } - ), - - localeData : function () { - return this._locale; - } - }); - - function rawMonthSetter(mom, value) { - var dayOfMonth; - - // TODO: Move this out of here! - if (typeof value === 'string') { - value = mom.localeData().monthsParse(value); - // TODO: Another silent failure? - if (typeof value !== 'number') { - return mom; - } - } - - dayOfMonth = Math.min(mom.date(), - daysInMonth(mom.year(), value)); - mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); - return mom; - } - - function rawGetter(mom, unit) { - return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit](); - } - - function rawSetter(mom, unit, value) { - if (unit === 'Month') { - return rawMonthSetter(mom, value); - } else { - return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); - } - } - - function makeAccessor(unit, keepTime) { - return function (value) { - if (value != null) { - rawSetter(this, unit, value); - moment.updateOffset(this, keepTime); - return this; - } else { - return rawGetter(this, unit); - } - }; - } - - moment.fn.millisecond = moment.fn.milliseconds = makeAccessor('Milliseconds', false); - moment.fn.second = moment.fn.seconds = makeAccessor('Seconds', false); - moment.fn.minute = moment.fn.minutes = makeAccessor('Minutes', false); - // Setting the hour should keep the time, because the user explicitly - // specified which hour he wants. So trying to maintain the same hour (in - // a new timezone) makes sense. Adding/subtracting hours does not follow - // this rule. - moment.fn.hour = moment.fn.hours = makeAccessor('Hours', true); - // moment.fn.month is defined separately - moment.fn.date = makeAccessor('Date', true); - moment.fn.dates = deprecate('dates accessor is deprecated. Use date instead.', makeAccessor('Date', true)); - moment.fn.year = makeAccessor('FullYear', true); - moment.fn.years = deprecate('years accessor is deprecated. Use year instead.', makeAccessor('FullYear', true)); - - // add plural methods - moment.fn.days = moment.fn.day; - moment.fn.months = moment.fn.month; - moment.fn.weeks = moment.fn.week; - moment.fn.isoWeeks = moment.fn.isoWeek; - moment.fn.quarters = moment.fn.quarter; - - // add aliased format methods - moment.fn.toJSON = moment.fn.toISOString; - - /************************************ - Duration Prototype - ************************************/ - - - function daysToYears (days) { - // 400 years have 146097 days (taking into account leap year rules) - return days * 400 / 146097; - } - - function yearsToDays (years) { - // years * 365 + absRound(years / 4) - - // absRound(years / 100) + absRound(years / 400); - return years * 146097 / 400; - } - - extend(moment.duration.fn = Duration.prototype, { - - _bubble : function () { - var milliseconds = this._milliseconds, - days = this._days, - months = this._months, - data = this._data, - seconds, minutes, hours, years = 0; - - // The following code bubbles up values, see the tests for - // examples of what that means. - data.milliseconds = milliseconds % 1000; - - seconds = absRound(milliseconds / 1000); - data.seconds = seconds % 60; - - minutes = absRound(seconds / 60); - data.minutes = minutes % 60; - - hours = absRound(minutes / 60); - data.hours = hours % 24; - - days += absRound(hours / 24); - - // Accurately convert days to years, assume start from year 0. - years = absRound(daysToYears(days)); - days -= absRound(yearsToDays(years)); - - // 30 days to a month - // TODO (iskren): Use anchor date (like 1st Jan) to compute this. - months += absRound(days / 30); - days %= 30; - - // 12 months -> 1 year - years += absRound(months / 12); - months %= 12; - - data.days = days; - data.months = months; - data.years = years; - }, - - abs : function () { - this._milliseconds = Math.abs(this._milliseconds); - this._days = Math.abs(this._days); - this._months = Math.abs(this._months); - - this._data.milliseconds = Math.abs(this._data.milliseconds); - this._data.seconds = Math.abs(this._data.seconds); - this._data.minutes = Math.abs(this._data.minutes); - this._data.hours = Math.abs(this._data.hours); - this._data.months = Math.abs(this._data.months); - this._data.years = Math.abs(this._data.years); - - return this; - }, - - weeks : function () { - return absRound(this.days() / 7); - }, - - valueOf : function () { - return this._milliseconds + - this._days * 864e5 + - (this._months % 12) * 2592e6 + - toInt(this._months / 12) * 31536e6; - }, - - humanize : function (withSuffix) { - var output = relativeTime(this, !withSuffix, this.localeData()); - - if (withSuffix) { - output = this.localeData().pastFuture(+this, output); - } - - return this.localeData().postformat(output); - }, - - add : function (input, val) { - // supports only 2.0-style add(1, 's') or add(moment) - var dur = moment.duration(input, val); - - this._milliseconds += dur._milliseconds; - this._days += dur._days; - this._months += dur._months; - - this._bubble(); - - return this; - }, - - subtract : function (input, val) { - var dur = moment.duration(input, val); - - this._milliseconds -= dur._milliseconds; - this._days -= dur._days; - this._months -= dur._months; - - this._bubble(); - - return this; - }, - - get : function (units) { - units = normalizeUnits(units); - return this[units.toLowerCase() + 's'](); - }, - - as : function (units) { - var days, months; - units = normalizeUnits(units); - - days = this._days + this._milliseconds / 864e5; - if (units === 'month' || units === 'year') { - months = this._months + daysToYears(days) * 12; - return units === 'month' ? months : months / 12; - } else { - days += yearsToDays(this._months / 12); - switch (units) { - case 'week': return days / 7; - case 'day': return days; - case 'hour': return days * 24; - case 'minute': return days * 24 * 60; - case 'second': return days * 24 * 60 * 60; - case 'millisecond': return days * 24 * 60 * 60 * 1000; - default: throw new Error('Unknown unit ' + units); - } - } - }, - - lang : moment.fn.lang, - locale : moment.fn.locale, - - toIsoString : deprecate( - "toIsoString() is deprecated. Please use toISOString() instead " + - "(notice the capitals)", - function () { - return this.toISOString(); - } - ), - - toISOString : function () { - // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js - var years = Math.abs(this.years()), - months = Math.abs(this.months()), - days = Math.abs(this.days()), - hours = Math.abs(this.hours()), - minutes = Math.abs(this.minutes()), - seconds = Math.abs(this.seconds() + this.milliseconds() / 1000); - - if (!this.asSeconds()) { - // this is the same as C#'s (Noda) and python (isodate)... - // but not other JS (goog.date) - return 'P0D'; - } - - return (this.asSeconds() < 0 ? '-' : '') + - 'P' + - (years ? years + 'Y' : '') + - (months ? months + 'M' : '') + - (days ? days + 'D' : '') + - ((hours || minutes || seconds) ? 'T' : '') + - (hours ? hours + 'H' : '') + - (minutes ? minutes + 'M' : '') + - (seconds ? seconds + 'S' : ''); - }, - - localeData : function () { - return this._locale; - } - }); - - function makeDurationGetter(name) { - moment.duration.fn[name] = function () { - return this._data[name]; - }; - } - - for (i in unitMillisecondFactors) { - if (unitMillisecondFactors.hasOwnProperty(i)) { - makeDurationGetter(i.toLowerCase()); - } - } - - moment.duration.fn.asMilliseconds = function () { - return this.as('ms'); - }; - moment.duration.fn.asSeconds = function () { - return this.as('s'); - }; - moment.duration.fn.asMinutes = function () { - return this.as('m'); - }; - moment.duration.fn.asHours = function () { - return this.as('h'); - }; - moment.duration.fn.asDays = function () { - return this.as('d'); - }; - moment.duration.fn.asWeeks = function () { - return this.as('weeks'); - }; - moment.duration.fn.asMonths = function () { - return this.as('M'); - }; - moment.duration.fn.asYears = function () { - return this.as('y'); - }; - - /************************************ - Default Locale - ************************************/ - - - // Set default locale, other locale will inherit from English. - moment.locale('en', { - ordinal : function (number) { - var b = number % 10, - output = (toInt(number % 100 / 10) === 1) ? 'th' : - (b === 1) ? 'st' : - (b === 2) ? 'nd' : - (b === 3) ? 'rd' : 'th'; - return number + output; - } - }); - - /* EMBED_LOCALES */ - - /************************************ - Exposing Moment - ************************************/ - - function makeGlobal(shouldDeprecate) { - /*global ender:false */ - if (typeof ender !== 'undefined') { - return; - } - oldGlobalMoment = globalScope.moment; - if (shouldDeprecate) { - globalScope.moment = deprecate( - 'Accessing Moment through the global scope is ' + - 'deprecated, and will be removed in an upcoming ' + - 'release.', - moment); - } else { - globalScope.moment = moment; - } - } - - // CommonJS module is defined - if (hasModule) { - module.exports = moment; - } else if (typeof define === 'function' && define.amd) { - define('moment', function (require, exports, module) { - if (module.config && module.config() && module.config().noGlobal === true) { - // release the global variable - globalScope.moment = oldGlobalMoment; - } - - return moment; - }); - makeGlobal(true); - } else { - makeGlobal(); - } -}).call(this); \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/nv.d3.js b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/nv.d3.js deleted file mode 100644 index 949a04d5..00000000 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/nv.d3.js +++ /dev/null @@ -1,13435 +0,0 @@ -/* nvd3 version 1.8.1 (https://github.com/novus/nvd3) 2015-06-17 */ -(function(){ - -// set up main nv object - var nv = {}; - -// the major global objects under the nv namespace - nv.dev = false; //set false when in production - nv.tooltip = nv.tooltip || {}; // For the tooltip system - nv.utils = nv.utils || {}; // Utility subsystem - nv.models = nv.models || {}; //stores all the possible models/components - nv.charts = {}; //stores all the ready to use charts - nv.logs = {}; //stores some statistics and potential error messages - nv.dom = {}; //DOM manipulation functions - - nv.dispatch = d3.dispatch('render_start', 'render_end'); - -// Function bind polyfill -// Needed ONLY for phantomJS as it's missing until version 2.0 which is unreleased as of this comment -// https://github.com/ariya/phantomjs/issues/10522 -// http://kangax.github.io/compat-table/es5/#Function.prototype.bind -// phantomJS is used for running the test suite - if (!Function.prototype.bind) { - Function.prototype.bind = function (oThis) { - if (typeof this !== "function") { - // closest thing possible to the ECMAScript 5 internal IsCallable function - throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); - } - - var aArgs = Array.prototype.slice.call(arguments, 1), - fToBind = this, - fNOP = function () {}, - fBound = function () { - return fToBind.apply(this instanceof fNOP && oThis - ? this - : oThis, - aArgs.concat(Array.prototype.slice.call(arguments))); - }; - - fNOP.prototype = this.prototype; - fBound.prototype = new fNOP(); - return fBound; - }; - } - -// Development render timers - disabled if dev = false - if (nv.dev) { - nv.dispatch.on('render_start', function(e) { - nv.logs.startTime = +new Date(); - }); - - nv.dispatch.on('render_end', function(e) { - nv.logs.endTime = +new Date(); - nv.logs.totalTime = nv.logs.endTime - nv.logs.startTime; - nv.log('total', nv.logs.totalTime); // used for development, to keep track of graph generation times - }); - } - -// Logs all arguments, and returns the last so you can test things in place -// Note: in IE8 console.log is an object not a function, and if modernizr is used -// then calling Function.prototype.bind with with anything other than a function -// causes a TypeError to be thrown. - nv.log = function() { - if (nv.dev && window.console && console.log && console.log.apply) - console.log.apply(console, arguments); - else if (nv.dev && window.console && typeof console.log == "function" && Function.prototype.bind) { - var log = Function.prototype.bind.call(console.log, console); - log.apply(console, arguments); - } - return arguments[arguments.length - 1]; - }; - -// print console warning, should be used by deprecated functions - nv.deprecated = function(name, info) { - if (console && console.warn) { - console.warn('nvd3 warning: `' + name + '` has been deprecated. ', info || ''); - } - }; - -// The nv.render function is used to queue up chart rendering -// in non-blocking async functions. -// When all queued charts are done rendering, nv.dispatch.render_end is invoked. - nv.render = function render(step) { - // number of graphs to generate in each timeout loop - step = step || 1; - - nv.render.active = true; - nv.dispatch.render_start(); - - var renderLoop = function() { - var chart, graph; - - for (var i = 0; i < step && (graph = nv.render.queue[i]); i++) { - chart = graph.generate(); - if (typeof graph.callback == typeof(Function)) graph.callback(chart); - } - - nv.render.queue.splice(0, i); - - if (nv.render.queue.length) { - setTimeout(renderLoop); - } - else { - nv.dispatch.render_end(); - nv.render.active = false; - } - }; - - setTimeout(renderLoop); - }; - - nv.render.active = false; - nv.render.queue = []; - - /* - Adds a chart to the async rendering queue. This method can take arguments in two forms: - nv.addGraph({ - generate: - callback: - }) - - or - - nv.addGraph(, ) - - The generate function should contain code that creates the NVD3 model, sets options - on it, adds data to an SVG element, and invokes the chart model. The generate function - should return the chart model. See examples/lineChart.html for a usage example. - - The callback function is optional, and it is called when the generate function completes. - */ - nv.addGraph = function(obj) { - if (typeof arguments[0] === typeof(Function)) { - obj = {generate: arguments[0], callback: arguments[1]}; - } - - nv.render.queue.push(obj); - - if (!nv.render.active) { - nv.render(); - } - }; - -// Node/CommonJS exports - if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined') { - module.exports = nv; - } - - if (typeof(window) !== 'undefined') { - window.nv = nv; - } - /* Facade for queueing DOM write operations - * with Fastdom (https://github.com/wilsonpage/fastdom) - * if available. - * This could easily be extended to support alternate - * implementations in the future. - */ - nv.dom.write = function(callback) { - if (window.fastdom !== undefined) { - return fastdom.write(callback); - } - return callback(); - }; - - /* Facade for queueing DOM read operations - * with Fastdom (https://github.com/wilsonpage/fastdom) - * if available. - * This could easily be extended to support alternate - * implementations in the future. - */ - nv.dom.read = function(callback) { - if (window.fastdom !== undefined) { - return fastdom.read(callback); - } - return callback(); - };/* Utility class to handle creation of an interactive layer. - This places a rectangle on top of the chart. When you mouse move over it, it sends a dispatch - containing the X-coordinate. It can also render a vertical line where the mouse is located. - - dispatch.elementMousemove is the important event to latch onto. It is fired whenever the mouse moves over - the rectangle. The dispatch is given one object which contains the mouseX/Y location. - It also has 'pointXValue', which is the conversion of mouseX to the x-axis scale. - */ - nv.interactiveGuideline = function() { - "use strict"; - - var tooltip = nv.models.tooltip(); - tooltip.duration(0).hideDelay(0)._isInteractiveLayer(true).hidden(false); - - //Public settings - var width = null; - var height = null; - - //Please pass in the bounding chart's top and left margins - //This is important for calculating the correct mouseX/Y positions. - var margin = {left: 0, top: 0} - , xScale = d3.scale.linear() - , dispatch = d3.dispatch('elementMousemove', 'elementMouseout', 'elementClick', 'elementDblclick') - , showGuideLine = true; - //Must pass in the bounding chart's container. - //The mousemove event is attached to this container. - var svgContainer = null; - - // check if IE by looking for activeX - var isMSIE = "ActiveXObject" in window; - - - function layer(selection) { - selection.each(function(data) { - var container = d3.select(this); - var availableWidth = (width || 960), availableHeight = (height || 400); - var wrap = container.selectAll("g.nv-wrap.nv-interactiveLineLayer") - .data([data]); - var wrapEnter = wrap.enter() - .append("g").attr("class", " nv-wrap nv-interactiveLineLayer"); - wrapEnter.append("g").attr("class","nv-interactiveGuideLine"); - - if (!svgContainer) { - return; - } - - function mouseHandler() { - var d3mouse = d3.mouse(this); - var mouseX = d3mouse[0]; - var mouseY = d3mouse[1]; - var subtractMargin = true; - var mouseOutAnyReason = false; - if (isMSIE) { - /* - D3.js (or maybe SVG.getScreenCTM) has a nasty bug in Internet Explorer 10. - d3.mouse() returns incorrect X,Y mouse coordinates when mouse moving - over a rect in IE 10. - However, d3.event.offsetX/Y also returns the mouse coordinates - relative to the triggering . So we use offsetX/Y on IE. - */ - mouseX = d3.event.offsetX; - mouseY = d3.event.offsetY; - - /* - On IE, if you attach a mouse event listener to the container, - it will actually trigger it for all the child elements (like , , etc). - When this happens on IE, the offsetX/Y is set to where ever the child element - is located. - As a result, we do NOT need to subtract margins to figure out the mouse X/Y - position under this scenario. Removing the line below *will* cause - the interactive layer to not work right on IE. - */ - if(d3.event.target.tagName !== "svg") { - subtractMargin = false; - } - - if (d3.event.target.className.baseVal.match("nv-legend")) { - mouseOutAnyReason = true; - } - - } - - if(subtractMargin) { - mouseX -= margin.left; - mouseY -= margin.top; - } - - /* If mouseX/Y is outside of the chart's bounds, - trigger a mouseOut event. - */ - if (mouseX < 0 || mouseY < 0 - || mouseX > availableWidth || mouseY > availableHeight - || (d3.event.relatedTarget && d3.event.relatedTarget.ownerSVGElement === undefined) - || mouseOutAnyReason - ) { - - if (isMSIE) { - if (d3.event.relatedTarget - && d3.event.relatedTarget.ownerSVGElement === undefined - && (d3.event.relatedTarget.className === undefined - || d3.event.relatedTarget.className.match(tooltip.nvPointerEventsClass))) { - - return; - } - } - dispatch.elementMouseout({ - mouseX: mouseX, - mouseY: mouseY - }); - layer.renderGuideLine(null); //hide the guideline - tooltip.hidden(true); - return; - } else { - tooltip.hidden(false); - } - - var pointXValue = xScale.invert(mouseX); - dispatch.elementMousemove({ - mouseX: mouseX, - mouseY: mouseY, - pointXValue: pointXValue - }); - - //If user double clicks the layer, fire a elementDblclick - if (d3.event.type === "dblclick") { - dispatch.elementDblclick({ - mouseX: mouseX, - mouseY: mouseY, - pointXValue: pointXValue - }); - } - - // if user single clicks the layer, fire elementClick - if (d3.event.type === 'click') { - dispatch.elementClick({ - mouseX: mouseX, - mouseY: mouseY, - pointXValue: pointXValue - }); - } - } - - svgContainer - .on("touchmove",mouseHandler) - .on("mousemove",mouseHandler, true) - .on("mouseout" ,mouseHandler,true) - .on("dblclick" ,mouseHandler) - .on("click", mouseHandler) - ; - - layer.guideLine = null; - //Draws a vertical guideline at the given X postion. - layer.renderGuideLine = function(x) { - if (!showGuideLine) return; - if (layer.guideLine && layer.guideLine.attr("x1") === x) return; - nv.dom.write(function() { - var line = wrap.select(".nv-interactiveGuideLine") - .selectAll("line") - .data((x != null) ? [nv.utils.NaNtoZero(x)] : [], String); - line.enter() - .append("line") - .attr("class", "nv-guideline") - .attr("x1", function(d) { return d;}) - .attr("x2", function(d) { return d;}) - .attr("y1", availableHeight) - .attr("y2",0); - line.exit().remove(); - }); - } - }); - } - - layer.dispatch = dispatch; - layer.tooltip = tooltip; - - layer.margin = function(_) { - if (!arguments.length) return margin; - margin.top = typeof _.top != 'undefined' ? _.top : margin.top; - margin.left = typeof _.left != 'undefined' ? _.left : margin.left; - return layer; - }; - - layer.width = function(_) { - if (!arguments.length) return width; - width = _; - return layer; - }; - - layer.height = function(_) { - if (!arguments.length) return height; - height = _; - return layer; - }; - - layer.xScale = function(_) { - if (!arguments.length) return xScale; - xScale = _; - return layer; - }; - - layer.showGuideLine = function(_) { - if (!arguments.length) return showGuideLine; - showGuideLine = _; - return layer; - }; - - layer.svgContainer = function(_) { - if (!arguments.length) return svgContainer; - svgContainer = _; - return layer; - }; - - return layer; - }; - - /* Utility class that uses d3.bisect to find the index in a given array, where a search value can be inserted. - This is different from normal bisectLeft; this function finds the nearest index to insert the search value. - - For instance, lets say your array is [1,2,3,5,10,30], and you search for 28. - Normal d3.bisectLeft will return 4, because 28 is inserted after the number 10. But interactiveBisect will return 5 - because 28 is closer to 30 than 10. - - Unit tests can be found in: interactiveBisectTest.html - - Has the following known issues: - * Will not work if the data points move backwards (ie, 10,9,8,7, etc) or if the data points are in random order. - * Won't work if there are duplicate x coordinate values. - */ - nv.interactiveBisect = function (values, searchVal, xAccessor) { - "use strict"; - if (! (values instanceof Array)) { - return null; - } - var _xAccessor; - if (typeof xAccessor !== 'function') { - _xAccessor = function(d) { - return d.x; - } - } else { - _xAccessor = xAccessor; - } - var _cmp = function(d, v) { - // Accessors are no longer passed the index of the element along with - // the element itself when invoked by d3.bisector. - // - // Starting at D3 v3.4.4, d3.bisector() started inspecting the - // function passed to determine if it should consider it an accessor - // or a comparator. This meant that accessors that take two arguments - // (expecting an index as the second parameter) are treated as - // comparators where the second argument is the search value against - // which the first argument is compared. - return _xAccessor(d) - v; - }; - - var bisect = d3.bisector(_cmp).left; - var index = d3.max([0, bisect(values,searchVal) - 1]); - var currentValue = _xAccessor(values[index]); - - if (typeof currentValue === 'undefined') { - currentValue = index; - } - - if (currentValue === searchVal) { - return index; //found exact match - } - - var nextIndex = d3.min([index+1, values.length - 1]); - var nextValue = _xAccessor(values[nextIndex]); - - if (typeof nextValue === 'undefined') { - nextValue = nextIndex; - } - - if (Math.abs(nextValue - searchVal) >= Math.abs(currentValue - searchVal)) { - return index; - } else { - return nextIndex - } - }; - - /* - Returns the index in the array "values" that is closest to searchVal. - Only returns an index if searchVal is within some "threshold". - Otherwise, returns null. - */ - nv.nearestValueIndex = function (values, searchVal, threshold) { - "use strict"; - var yDistMax = Infinity, indexToHighlight = null; - values.forEach(function(d,i) { - var delta = Math.abs(searchVal - d); - if ( d != null && delta <= yDistMax && delta < threshold) { - yDistMax = delta; - indexToHighlight = i; - } - }); - return indexToHighlight; - }; - /* Tooltip rendering model for nvd3 charts. - window.nv.models.tooltip is the updated,new way to render tooltips. - - window.nv.tooltip.show is the old tooltip code. - window.nv.tooltip.* also has various helper methods. - */ - (function() { - "use strict"; - - /* Model which can be instantiated to handle tooltip rendering. - Example usage: - var tip = nv.models.tooltip().gravity('w').distance(23) - .data(myDataObject); - - tip(); //just invoke the returned function to render tooltip. - */ - nv.models.tooltip = function() { - - /* - Tooltip data. If data is given in the proper format, a consistent tooltip is generated. - Example Format of data: - { - key: "Date", - value: "August 2009", - series: [ - {key: "Series 1", value: "Value 1", color: "#000"}, - {key: "Series 2", value: "Value 2", color: "#00f"} - ] - } - */ - var data = null; - var gravity = 'w' //Can be 'n','s','e','w'. Determines how tooltip is positioned. - , distance = 25 //Distance to offset tooltip from the mouse location. - , snapDistance = 0 //Tolerance allowed before tooltip is moved from its current position (creates 'snapping' effect) - , fixedTop = null //If not null, this fixes the top position of the tooltip. - , classes = null //Attaches additional CSS classes to the tooltip DIV that is created. - , chartContainer = null //Parent dom element of the SVG that holds the chart. - , hidden = true // start off hidden, toggle with hide/show functions below - , hideDelay = 400 // delay before the tooltip hides after calling hide() - , tooltip = null // d3 select of tooltipElem below - , tooltipElem = null //actual DOM element representing the tooltip. - , position = {left: null, top: null} //Relative position of the tooltip inside chartContainer. - , offset = {left: 0, top: 0} //Offset of tooltip against the pointer - , enabled = true //True -> tooltips are rendered. False -> don't render tooltips. - , duration = 100 // duration for tooltip movement - , headerEnabled = true - ; - - // set to true by interactive layer to adjust tooltip positions - // eventually we should probably fix interactive layer to get the position better. - // for now this is needed if you want to set chartContainer for normal tooltips, else it "fixes" it to broken - var isInteractiveLayer = false; - - //Generates a unique id when you create a new tooltip() object - var id = "nvtooltip-" + Math.floor(Math.random() * 100000); - - //CSS class to specify whether element should not have mouse events. - var nvPointerEventsClass = "nv-pointer-events-none"; - - //Format function for the tooltip values column - var valueFormatter = function(d,i) { - return d; - }; - - //Format function for the tooltip header value. - var headerFormatter = function(d) { - return d; - }; - - var keyFormatter = function(d, i) { - return d; - }; - - //By default, the tooltip model renders a beautiful table inside a DIV. - //You can override this function if a custom tooltip is desired. - var contentGenerator = function(d) { - if (d === null) { - return ''; - } - - var table = d3.select(document.createElement("table")); - if (headerEnabled) { - var theadEnter = table.selectAll("thead") - .data([d]) - .enter().append("thead"); - - theadEnter.append("tr") - .append("td") - .attr("colspan", 3) - .append("strong") - .classed("x-value", true) - .html(headerFormatter(d.value)); - } - - var tbodyEnter = table.selectAll("tbody") - .data([d]) - .enter().append("tbody"); - - var trowEnter = tbodyEnter.selectAll("tr") - .data(function(p) { return p.series}) - .enter() - .append("tr") - .classed("highlight", function(p) { return p.highlight}); - - trowEnter.append("td") - .classed("legend-color-guide",true) - .append("div") - .style("background-color", function(p) { return p.color}); - - trowEnter.append("td") - .classed("key",true) - .html(function(p, i) {return keyFormatter(p.key, i)}); - - trowEnter.append("td") - .classed("value",true) - .html(function(p, i) { return valueFormatter(p.value, i) }); - - - trowEnter.selectAll("td").each(function(p) { - if (p.highlight) { - var opacityScale = d3.scale.linear().domain([0,1]).range(["#fff",p.color]); - var opacity = 0.6; - d3.select(this) - .style("border-bottom-color", opacityScale(opacity)) - .style("border-top-color", opacityScale(opacity)) - ; - } - }); - - var html = table.node().outerHTML; - if (d.footer !== undefined) - html += ""; - return html; - - }; - - var dataSeriesExists = function(d) { - if (d && d.series) { - if (d.series instanceof Array) { - return !!d.series.length; - } - // if object, it's okay just convert to array of the object - if (d.series instanceof Object) { - d.series = [d.series]; - return true; - } - } - return false; - }; - - var calcTooltipPosition = function(pos) { - if (!tooltipElem) return; - - nv.dom.read(function() { - var height = parseInt(tooltipElem.offsetHeight, 10), - width = parseInt(tooltipElem.offsetWidth, 10), - windowWidth = nv.utils.windowSize().width, - windowHeight = nv.utils.windowSize().height, - scrollTop = window.pageYOffset, - scrollLeft = window.pageXOffset, - left, top; - - windowHeight = window.innerWidth >= document.body.scrollWidth ? windowHeight : windowHeight - 16; - windowWidth = window.innerHeight >= document.body.scrollHeight ? windowWidth : windowWidth - 16; - - - //Helper functions to find the total offsets of a given DOM element. - //Looks up the entire ancestry of an element, up to the first relatively positioned element. - var tooltipTop = function ( Elem ) { - var offsetTop = top; - do { - if( !isNaN( Elem.offsetTop ) ) { - offsetTop += (Elem.offsetTop); - } - Elem = Elem.offsetParent; - } while( Elem ); - return offsetTop; - }; - var tooltipLeft = function ( Elem ) { - var offsetLeft = left; - do { - if( !isNaN( Elem.offsetLeft ) ) { - offsetLeft += (Elem.offsetLeft); - } - Elem = Elem.offsetParent; - } while( Elem ); - return offsetLeft; - }; - - // calculate position based on gravity - var tLeft, tTop; - switch (gravity) { - case 'e': - left = pos[0] - width - distance; - top = pos[1] - (height / 2); - tLeft = tooltipLeft(tooltipElem); - tTop = tooltipTop(tooltipElem); - if (tLeft < scrollLeft) left = pos[0] + distance > scrollLeft ? pos[0] + distance : scrollLeft - tLeft + left; - if (tTop < scrollTop) top = scrollTop - tTop + top; - if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height; - break; - case 'w': - left = pos[0] + distance; - top = pos[1] - (height / 2); - tLeft = tooltipLeft(tooltipElem); - tTop = tooltipTop(tooltipElem); - if (tLeft + width > windowWidth) left = pos[0] - width - distance; - if (tTop < scrollTop) top = scrollTop + 5; - if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height; - break; - case 'n': - left = pos[0] - (width / 2) - 5; - top = pos[1] + distance; - tLeft = tooltipLeft(tooltipElem); - tTop = tooltipTop(tooltipElem); - if (tLeft < scrollLeft) left = scrollLeft + 5; - if (tLeft + width > windowWidth) left = left - width/2 + 5; - if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height; - break; - case 's': - left = pos[0] - (width / 2); - top = pos[1] - height - distance; - tLeft = tooltipLeft(tooltipElem); - tTop = tooltipTop(tooltipElem); - if (tLeft < scrollLeft) left = scrollLeft + 5; - if (tLeft + width > windowWidth) left = left - width/2 + 5; - if (scrollTop > tTop) top = scrollTop; - break; - case 'none': - left = pos[0]; - top = pos[1] - distance; - tLeft = tooltipLeft(tooltipElem); - tTop = tooltipTop(tooltipElem); - break; - } - - // adjust tooltip offsets - left -= offset.left; - top -= offset.top; - - // using tooltip.style('transform') returns values un-usable for tween - var box = tooltipElem.getBoundingClientRect(); - var scrollTop = window.pageYOffset || document.documentElement.scrollTop; - var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft; - var old_translate = 'translate(' + (box.left + scrollLeft) + 'px, ' + (box.top + scrollTop) + 'px)'; - var new_translate = 'translate(' + left + 'px, ' + top + 'px)'; - var translateInterpolator = d3.interpolateString(old_translate, new_translate); - - var is_hidden = tooltip.style('opacity') < 0.1; - - // delay hiding a bit to avoid flickering - if (hidden) { - tooltip - .transition() - .delay(hideDelay) - .duration(0) - .style('opacity', 0); - } else { - tooltip - .interrupt() // cancel running transitions - .transition() - .duration(is_hidden ? 0 : duration) - // using tween since some versions of d3 can't auto-tween a translate on a div - .styleTween('transform', function (d) { - return translateInterpolator; - }, 'important') - // Safari has its own `-webkit-transform` and does not support `transform` - // transform tooltip without transition only in Safari - .style('-webkit-transform', new_translate) - .style('opacity', 1); - } - - - - }); - }; - - //In situations where the chart is in a 'viewBox', re-position the tooltip based on how far chart is zoomed. - function convertViewBoxRatio() { - if (chartContainer) { - var svg = d3.select(chartContainer); - if (svg.node().tagName !== "svg") { - svg = svg.select("svg"); - } - var viewBox = (svg.node()) ? svg.attr('viewBox') : null; - if (viewBox) { - viewBox = viewBox.split(' '); - var ratio = parseInt(svg.style('width'), 10) / viewBox[2]; - - position.left = position.left * ratio; - position.top = position.top * ratio; - } - } - } - - //Creates new tooltip container, or uses existing one on DOM. - function initTooltip() { - if (!tooltip) { - var body; - if (chartContainer) { - body = chartContainer; - } else { - body = document.body; - } - //Create new tooltip div if it doesn't exist on DOM. - tooltip = d3.select(body).append("div") - .attr("class", "nvtooltip " + (classes ? classes : "xy-tooltip")) - .attr("id", id); - tooltip.style("top", 0).style("left", 0); - tooltip.style('opacity', 0); - tooltip.selectAll("div, table, td, tr").classed(nvPointerEventsClass, true); - tooltip.classed(nvPointerEventsClass, true); - tooltipElem = tooltip.node(); - } - } - - //Draw the tooltip onto the DOM. - function nvtooltip() { - if (!enabled) return; - if (!dataSeriesExists(data)) return; - - convertViewBoxRatio(); - - var left = position.left; - var top = (fixedTop !== null) ? fixedTop : position.top; - - nv.dom.write(function () { - initTooltip(); - // generate data and set it into tooltip - // Bonus - If you override contentGenerator and return falsey you can use something like - // React or Knockout to bind the data for your tooltip - var newContent = contentGenerator(data); - if (newContent) { - tooltipElem.innerHTML = newContent; - } - - if (chartContainer && isInteractiveLayer) { - nv.dom.read(function() { - var svgComp = chartContainer.getElementsByTagName("svg")[0]; - var svgOffset = {left:0,top:0}; - if (svgComp) { - var svgBound = svgComp.getBoundingClientRect(); - var chartBound = chartContainer.getBoundingClientRect(); - var svgBoundTop = svgBound.top; - - //Defensive code. Sometimes, svgBoundTop can be a really negative - // number, like -134254. That's a bug. - // If such a number is found, use zero instead. FireFox bug only - if (svgBoundTop < 0) { - var containerBound = chartContainer.getBoundingClientRect(); - svgBoundTop = (Math.abs(svgBoundTop) > containerBound.height) ? 0 : svgBoundTop; - } - svgOffset.top = Math.abs(svgBoundTop - chartBound.top); - svgOffset.left = Math.abs(svgBound.left - chartBound.left); - } - //If the parent container is an overflow
    with scrollbars, subtract the scroll offsets. - //You need to also add any offset between the element and its containing
    - //Finally, add any offset of the containing
    on the whole page. - left += chartContainer.offsetLeft + svgOffset.left - 2*chartContainer.scrollLeft; - top += chartContainer.offsetTop + svgOffset.top - 2*chartContainer.scrollTop; - - if (snapDistance && snapDistance > 0) { - top = Math.floor(top/snapDistance) * snapDistance; - } - calcTooltipPosition([left,top]); - }); - } else { - calcTooltipPosition([left,top]); - } - }); - - return nvtooltip; - } - - nvtooltip.nvPointerEventsClass = nvPointerEventsClass; - nvtooltip.options = nv.utils.optionsFunc.bind(nvtooltip); - - nvtooltip._options = Object.create({}, { - // simple read/write options - duration: {get: function(){return duration;}, set: function(_){duration=_;}}, - gravity: {get: function(){return gravity;}, set: function(_){gravity=_;}}, - distance: {get: function(){return distance;}, set: function(_){distance=_;}}, - snapDistance: {get: function(){return snapDistance;}, set: function(_){snapDistance=_;}}, - classes: {get: function(){return classes;}, set: function(_){classes=_;}}, - chartContainer: {get: function(){return chartContainer;}, set: function(_){chartContainer=_;}}, - fixedTop: {get: function(){return fixedTop;}, set: function(_){fixedTop=_;}}, - enabled: {get: function(){return enabled;}, set: function(_){enabled=_;}}, - hideDelay: {get: function(){return hideDelay;}, set: function(_){hideDelay=_;}}, - contentGenerator: {get: function(){return contentGenerator;}, set: function(_){contentGenerator=_;}}, - valueFormatter: {get: function(){return valueFormatter;}, set: function(_){valueFormatter=_;}}, - headerFormatter: {get: function(){return headerFormatter;}, set: function(_){headerFormatter=_;}}, - keyFormatter: {get: function(){return keyFormatter;}, set: function(_){keyFormatter=_;}}, - headerEnabled: {get: function(){return headerEnabled;}, set: function(_){headerEnabled=_;}}, - - // internal use only, set by interactive layer to adjust position. - _isInteractiveLayer: {get: function(){return isInteractiveLayer;}, set: function(_){isInteractiveLayer=!!_;}}, - - // options with extra logic - position: {get: function(){return position;}, set: function(_){ - position.left = _.left !== undefined ? _.left : position.left; - position.top = _.top !== undefined ? _.top : position.top; - }}, - offset: {get: function(){return offset;}, set: function(_){ - offset.left = _.left !== undefined ? _.left : offset.left; - offset.top = _.top !== undefined ? _.top : offset.top; - }}, - hidden: {get: function(){return hidden;}, set: function(_){ - if (hidden != _) { - hidden = !!_; - nvtooltip(); - } - }}, - data: {get: function(){return data;}, set: function(_){ - // if showing a single data point, adjust data format with that - if (_.point) { - _.value = _.point.x; - _.series = _.series || {}; - _.series.value = _.point.y; - _.series.color = _.point.color || _.series.color; - } - data = _; - }}, - - // read only properties - tooltipElem: {get: function(){return tooltipElem;}, set: function(_){}}, - id: {get: function(){return id;}, set: function(_){}} - }); - - nv.utils.initOptions(nvtooltip); - return nvtooltip; - }; - - })(); - - - /* - Gets the browser window size - - Returns object with height and width properties - */ - nv.utils.windowSize = function() { - // Sane defaults - var size = {width: 640, height: 480}; - - // Most recent browsers use - if (window.innerWidth && window.innerHeight) { - size.width = window.innerWidth; - size.height = window.innerHeight; - return (size); - } - - // IE can use depending on mode it is in - if (document.compatMode=='CSS1Compat' && - document.documentElement && - document.documentElement.offsetWidth ) { - - size.width = document.documentElement.offsetWidth; - size.height = document.documentElement.offsetHeight; - return (size); - } - - // Earlier IE uses Doc.body - if (document.body && document.body.offsetWidth) { - size.width = document.body.offsetWidth; - size.height = document.body.offsetHeight; - return (size); - } - - return (size); - }; - - /* - Binds callback function to run when window is resized - */ - nv.utils.windowResize = function(handler) { - if (window.addEventListener) { - window.addEventListener('resize', handler); - } else { - nv.log("ERROR: Failed to bind to window.resize with: ", handler); - } - // return object with clear function to remove the single added callback. - return { - callback: handler, - clear: function() { - window.removeEventListener('resize', handler); - } - } - }; - - - /* - Backwards compatible way to implement more d3-like coloring of graphs. - Can take in nothing, an array, or a function/scale - To use a normal scale, get the range and pass that because we must be able - to take two arguments and use the index to keep backward compatibility - */ - nv.utils.getColor = function(color) { - //if you pass in nothing, get default colors back - if (color === undefined) { - return nv.utils.defaultColor(); - - //if passed an array, turn it into a color scale - // use isArray, instanceof fails if d3 range is created in an iframe - } else if(Array.isArray(color)) { - var color_scale = d3.scale.ordinal().range(color); - return function(d, i) { - var key = i === undefined ? d : i; - return d.color || color_scale(key); - }; - - //if passed a function or scale, return it, or whatever it may be - //external libs, such as angularjs-nvd3-directives use this - } else { - //can't really help it if someone passes rubbish as color - return color; - } - }; - - - /* - Default color chooser uses a color scale of 20 colors from D3 - https://github.com/mbostock/d3/wiki/Ordinal-Scales#categorical-colors - */ - nv.utils.defaultColor = function() { - // get range of the scale so we'll turn it into our own function. - return nv.utils.getColor(d3.scale.category20().range()); - }; - - - /* - Returns a color function that takes the result of 'getKey' for each series and - looks for a corresponding color from the dictionary - */ - nv.utils.customTheme = function(dictionary, getKey, defaultColors) { - // use default series.key if getKey is undefined - getKey = getKey || function(series) { return series.key }; - defaultColors = defaultColors || d3.scale.category20().range(); - - // start at end of default color list and walk back to index 0 - var defIndex = defaultColors.length; - - return function(series, index) { - var key = getKey(series); - if (typeof dictionary[key] === 'function') { - return dictionary[key](); - } else if (dictionary[key] !== undefined) { - return dictionary[key]; - } else { - // no match in dictionary, use a default color - if (!defIndex) { - // used all the default colors, start over - defIndex = defaultColors.length; - } - defIndex = defIndex - 1; - return defaultColors[defIndex]; - } - }; - }; - - - /* - From the PJAX example on d3js.org, while this is not really directly needed - it's a very cool method for doing pjax, I may expand upon it a little bit, - open to suggestions on anything that may be useful - */ - nv.utils.pjax = function(links, content) { - - var load = function(href) { - d3.html(href, function(fragment) { - var target = d3.select(content).node(); - target.parentNode.replaceChild( - d3.select(fragment).select(content).node(), - target); - nv.utils.pjax(links, content); - }); - }; - - d3.selectAll(links).on("click", function() { - history.pushState(this.href, this.textContent, this.href); - load(this.href); - d3.event.preventDefault(); - }); - - d3.select(window).on("popstate", function() { - if (d3.event.state) { - load(d3.event.state); - } - }); - }; - - - /* - For when we want to approximate the width in pixels for an SVG:text element. - Most common instance is when the element is in a display:none; container. - Forumla is : text.length * font-size * constant_factor - */ - nv.utils.calcApproxTextWidth = function (svgTextElem) { - if (typeof svgTextElem.style === 'function' - && typeof svgTextElem.text === 'function') { - - var fontSize = parseInt(svgTextElem.style("font-size").replace("px",""), 10); - var textLength = svgTextElem.text().length; - return textLength * fontSize * 0.5; - } - return 0; - }; - - - /* - Numbers that are undefined, null or NaN, convert them to zeros. - */ - nv.utils.NaNtoZero = function(n) { - if (typeof n !== 'number' - || isNaN(n) - || n === null - || n === Infinity - || n === -Infinity) { - - return 0; - } - return n; - }; - - /* - Add a way to watch for d3 transition ends to d3 - */ - d3.selection.prototype.watchTransition = function(renderWatch){ - var args = [this].concat([].slice.call(arguments, 1)); - return renderWatch.transition.apply(renderWatch, args); - }; - - - /* - Helper object to watch when d3 has rendered something - */ - nv.utils.renderWatch = function(dispatch, duration) { - if (!(this instanceof nv.utils.renderWatch)) { - return new nv.utils.renderWatch(dispatch, duration); - } - - var _duration = duration !== undefined ? duration : 250; - var renderStack = []; - var self = this; - - this.models = function(models) { - models = [].slice.call(arguments, 0); - models.forEach(function(model){ - model.__rendered = false; - (function(m){ - m.dispatch.on('renderEnd', function(arg){ - m.__rendered = true; - self.renderEnd('model'); - }); - })(model); - - if (renderStack.indexOf(model) < 0) { - renderStack.push(model); - } - }); - return this; - }; - - this.reset = function(duration) { - if (duration !== undefined) { - _duration = duration; - } - renderStack = []; - }; - - this.transition = function(selection, args, duration) { - args = arguments.length > 1 ? [].slice.call(arguments, 1) : []; - - if (args.length > 1) { - duration = args.pop(); - } else { - duration = _duration !== undefined ? _duration : 250; - } - selection.__rendered = false; - - if (renderStack.indexOf(selection) < 0) { - renderStack.push(selection); - } - - if (duration === 0) { - selection.__rendered = true; - selection.delay = function() { return this; }; - selection.duration = function() { return this; }; - return selection; - } else { - if (selection.length === 0) { - selection.__rendered = true; - } else if (selection.every( function(d){ return !d.length; } )) { - selection.__rendered = true; - } else { - selection.__rendered = false; - } - - var n = 0; - return selection - .transition() - .duration(duration) - .each(function(){ ++n; }) - .each('end', function(d, i) { - if (--n === 0) { - selection.__rendered = true; - self.renderEnd.apply(this, args); - } - }); - } - }; - - this.renderEnd = function() { - if (renderStack.every( function(d){ return d.__rendered; } )) { - renderStack.forEach( function(d){ d.__rendered = false; }); - dispatch.renderEnd.apply(this, arguments); - } - } - - }; - - - /* - Takes multiple objects and combines them into the first one (dst) - example: nv.utils.deepExtend({a: 1}, {a: 2, b: 3}, {c: 4}); - gives: {a: 2, b: 3, c: 4} - */ - nv.utils.deepExtend = function(dst){ - var sources = arguments.length > 1 ? [].slice.call(arguments, 1) : []; - sources.forEach(function(source) { - for (var key in source) { - var isArray = dst[key] instanceof Array; - var isObject = typeof dst[key] === 'object'; - var srcObj = typeof source[key] === 'object'; - - if (isObject && !isArray && srcObj) { - nv.utils.deepExtend(dst[key], source[key]); - } else { - dst[key] = source[key]; - } - } - }); - }; - - - /* - state utility object, used to track d3 states in the models - */ - nv.utils.state = function(){ - if (!(this instanceof nv.utils.state)) { - return new nv.utils.state(); - } - var state = {}; - var _self = this; - var _setState = function(){}; - var _getState = function(){ return {}; }; - var init = null; - var changed = null; - - this.dispatch = d3.dispatch('change', 'set'); - - this.dispatch.on('set', function(state){ - _setState(state, true); - }); - - this.getter = function(fn){ - _getState = fn; - return this; - }; - - this.setter = function(fn, callback) { - if (!callback) { - callback = function(){}; - } - _setState = function(state, update){ - fn(state); - if (update) { - callback(); - } - }; - return this; - }; - - this.init = function(state){ - init = init || {}; - nv.utils.deepExtend(init, state); - }; - - var _set = function(){ - var settings = _getState(); - - if (JSON.stringify(settings) === JSON.stringify(state)) { - return false; - } - - for (var key in settings) { - if (state[key] === undefined) { - state[key] = {}; - } - state[key] = settings[key]; - changed = true; - } - return true; - }; - - this.update = function(){ - if (init) { - _setState(init, false); - init = null; - } - if (_set.call(this)) { - this.dispatch.change(state); - } - }; - - }; - - - /* - Snippet of code you can insert into each nv.models.* to give you the ability to - do things like: - chart.options({ - showXAxis: true, - tooltips: true - }); - - To enable in the chart: - chart.options = nv.utils.optionsFunc.bind(chart); - */ - nv.utils.optionsFunc = function(args) { - if (args) { - d3.map(args).forEach((function(key,value) { - if (typeof this[key] === "function") { - this[key](value); - } - }).bind(this)); - } - return this; - }; - - - /* - numTicks: requested number of ticks - data: the chart data - - returns the number of ticks to actually use on X axis, based on chart data - to avoid duplicate ticks with the same value - */ - nv.utils.calcTicksX = function(numTicks, data) { - // find max number of values from all data streams - var numValues = 1; - var i = 0; - for (i; i < data.length; i += 1) { - var stream_len = data[i] && data[i].values ? data[i].values.length : 0; - numValues = stream_len > numValues ? stream_len : numValues; - } - nv.log("Requested number of ticks: ", numTicks); - nv.log("Calculated max values to be: ", numValues); - // make sure we don't have more ticks than values to avoid duplicates - numTicks = numTicks > numValues ? numTicks = numValues - 1 : numTicks; - // make sure we have at least one tick - numTicks = numTicks < 1 ? 1 : numTicks; - // make sure it's an integer - numTicks = Math.floor(numTicks); - nv.log("Calculating tick count as: ", numTicks); - return numTicks; - }; - - - /* - returns number of ticks to actually use on Y axis, based on chart data - */ - nv.utils.calcTicksY = function(numTicks, data) { - // currently uses the same logic but we can adjust here if needed later - return nv.utils.calcTicksX(numTicks, data); - }; - - - /* - Add a particular option from an options object onto chart - Options exposed on a chart are a getter/setter function that returns chart - on set to mimic typical d3 option chaining, e.g. svg.option1('a').option2('b'); - - option objects should be generated via Object.create() to provide - the option of manipulating data via get/set functions. - */ - nv.utils.initOption = function(chart, name) { - // if it's a call option, just call it directly, otherwise do get/set - if (chart._calls && chart._calls[name]) { - chart[name] = chart._calls[name]; - } else { - chart[name] = function (_) { - if (!arguments.length) return chart._options[name]; - chart._overrides[name] = true; - chart._options[name] = _; - return chart; - }; - // calling the option as _option will ignore if set by option already - // so nvd3 can set options internally but the stop if set manually - chart['_' + name] = function(_) { - if (!arguments.length) return chart._options[name]; - if (!chart._overrides[name]) { - chart._options[name] = _; - } - return chart; - } - } - }; - - - /* - Add all options in an options object to the chart - */ - nv.utils.initOptions = function(chart) { - chart._overrides = chart._overrides || {}; - var ops = Object.getOwnPropertyNames(chart._options || {}); - var calls = Object.getOwnPropertyNames(chart._calls || {}); - ops = ops.concat(calls); - for (var i in ops) { - nv.utils.initOption(chart, ops[i]); - } - }; - - - /* - Inherit options from a D3 object - d3.rebind makes calling the function on target actually call it on source - Also use _d3options so we can track what we inherit for documentation and chained inheritance - */ - nv.utils.inheritOptionsD3 = function(target, d3_source, oplist) { - target._d3options = oplist.concat(target._d3options || []); - oplist.unshift(d3_source); - oplist.unshift(target); - d3.rebind.apply(this, oplist); - }; - - - /* - Remove duplicates from an array - */ - nv.utils.arrayUnique = function(a) { - return a.sort().filter(function(item, pos) { - return !pos || item != a[pos - 1]; - }); - }; - - - /* - Keeps a list of custom symbols to draw from in addition to d3.svg.symbol - Necessary since d3 doesn't let you extend its list -_- - Add new symbols by doing nv.utils.symbols.set('name', function(size){...}); - */ - nv.utils.symbolMap = d3.map(); - - - /* - Replaces d3.svg.symbol so that we can look both there and our own map - */ - nv.utils.symbol = function() { - var type, - size = 64; - function symbol(d,i) { - var t = type.call(this,d,i); - var s = size.call(this,d,i); - if (d3.svg.symbolTypes.indexOf(t) !== -1) { - return d3.svg.symbol().type(t).size(s)(); - } else { - return nv.utils.symbolMap.get(t)(s); - } - } - symbol.type = function(_) { - if (!arguments.length) return type; - type = d3.functor(_); - return symbol; - }; - symbol.size = function(_) { - if (!arguments.length) return size; - size = d3.functor(_); - return symbol; - }; - return symbol; - }; - - - /* - Inherit option getter/setter functions from source to target - d3.rebind makes calling the function on target actually call it on source - Also track via _inherited and _d3options so we can track what we inherit - for documentation generation purposes and chained inheritance - */ - nv.utils.inheritOptions = function(target, source) { - // inherit all the things - var ops = Object.getOwnPropertyNames(source._options || {}); - var calls = Object.getOwnPropertyNames(source._calls || {}); - var inherited = source._inherited || []; - var d3ops = source._d3options || []; - var args = ops.concat(calls).concat(inherited).concat(d3ops); - args.unshift(source); - args.unshift(target); - d3.rebind.apply(this, args); - // pass along the lists to keep track of them, don't allow duplicates - target._inherited = nv.utils.arrayUnique(ops.concat(calls).concat(inherited).concat(ops).concat(target._inherited || [])); - target._d3options = nv.utils.arrayUnique(d3ops.concat(target._d3options || [])); - }; - - - /* - Runs common initialize code on the svg before the chart builds - */ - nv.utils.initSVG = function(svg) { - svg.classed({'nvd3-svg':true}); - }; - - - /* - Sanitize and provide default for the container height. - */ - nv.utils.sanitizeHeight = function(height, container) { - return (height || parseInt(container.style('height'), 10) || 400); - }; - - - /* - Sanitize and provide default for the container width. - */ - nv.utils.sanitizeWidth = function(width, container) { - return (width || parseInt(container.style('width'), 10) || 960); - }; - - - /* - Calculate the available height for a chart. - */ - nv.utils.availableHeight = function(height, container, margin) { - return nv.utils.sanitizeHeight(height, container) - margin.top - margin.bottom; - }; - - /* - Calculate the available width for a chart. - */ - nv.utils.availableWidth = function(width, container, margin) { - return nv.utils.sanitizeWidth(width, container) - margin.left - margin.right; - }; - - /* - Clear any rendered chart components and display a chart's 'noData' message - */ - nv.utils.noData = function(chart, container) { - var opt = chart.options(), - margin = opt.margin(), - noData = opt.noData(), - data = (noData == null) ? ["No Data Available."] : [noData], - height = nv.utils.availableHeight(opt.height(), container, margin), - width = nv.utils.availableWidth(opt.width(), container, margin), - x = margin.left + width/2, - y = margin.top + height/2; - - //Remove any previously created chart components - container.selectAll('g').remove(); - - var noDataText = container.selectAll('.nv-noData').data(data); - - noDataText.enter().append('text') - .attr('class', 'nvd3 nv-noData') - .attr('dy', '-.7em') - .style('text-anchor', 'middle'); - - noDataText - .attr('x', x) - .attr('y', y) - .text(function(t){ return t; }); - }; - - nv.models.axis = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var axis = d3.svg.axis(); - var scale = d3.scale.linear(); - - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = 75 //only used for tickLabel currently - , height = 60 //only used for tickLabel currently - , axisLabelText = null - , showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes - , rotateLabels = 0 - , rotateYLabel = true - , staggerLabels = false - , isOrdinal = false - , ticks = null - , axisLabelDistance = 0 - , duration = 250 - , dispatch = d3.dispatch('renderEnd') - ; - axis - .scale(scale) - .orient('bottom') - .tickFormat(function(d) { return d }) - ; - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - var scale0; - var renderWatch = nv.utils.renderWatch(dispatch, duration); - - function chart(selection) { - renderWatch.reset(); - selection.each(function(data) { - var container = d3.select(this); - nv.utils.initSVG(container); - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); - - if (ticks !== null) - axis.ticks(ticks); - else if (axis.orient() == 'top' || axis.orient() == 'bottom') - axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100); - - //TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component - g.watchTransition(renderWatch, 'axis').call(axis); - - scale0 = scale0 || axis.scale(); - - var fmt = axis.tickFormat(); - if (fmt == null) { - fmt = scale0.tickFormat(); - } - - var axisLabel = g.selectAll('text.nv-axislabel') - .data([axisLabelText || null]); - axisLabel.exit().remove(); - - var xLabelMargin; - var axisMaxMin; - var w; - switch (axis.orient()) { - case 'top': - axisLabel.enter().append('text').attr('class', 'nv-axislabel'); - if (scale.range().length < 2) { - w = 0; - } else if (scale.range().length === 2) { - w = scale.range()[1]; - } else { - w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]); - } - axisLabel - .attr('text-anchor', 'middle') - .attr('y', 0) - .attr('x', w/2); - if (showMaxMin) { - axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') - .data(scale.domain()); - axisMaxMin.enter().append('g').attr('class',function(d,i){ - return ['nv-axisMaxMin','nv-axisMaxMin-x',(i == 0 ? 'nv-axisMin-x':'nv-axisMax-x')].join(' ') - }).append('text'); - axisMaxMin.exit().remove(); - axisMaxMin - .attr('transform', function(d,i) { - return 'translate(' + nv.utils.NaNtoZero(scale(d)) + ',0)' - }) - .select('text') - .attr('dy', '-0.5em') - .attr('y', -axis.tickPadding()) - .attr('text-anchor', 'middle') - .text(function(d,i) { - var v = fmt(d); - return ('' + v).match('NaN') ? '' : v; - }); - axisMaxMin.watchTransition(renderWatch, 'min-max top') - .attr('transform', function(d,i) { - return 'translate(' + nv.utils.NaNtoZero(scale.range()[i]) + ',0)' - }); - } - break; - case 'bottom': - xLabelMargin = axisLabelDistance + 36; - var maxTextWidth = 30; - var textHeight = 0; - var xTicks = g.selectAll('g').select("text"); - var rotateLabelsRule = ''; - if (rotateLabels%360) { - //Calculate the longest xTick width - xTicks.each(function(d,i){ - var box = this.getBoundingClientRect(); - var width = box.width; - textHeight = box.height; - if(width > maxTextWidth) maxTextWidth = width; - }); - rotateLabelsRule = 'rotate(' + rotateLabels + ' 0,' + (textHeight/2 + axis.tickPadding()) + ')'; - //Convert to radians before calculating sin. Add 30 to margin for healthy padding. - var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180)); - xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30; - //Rotate all xTicks - xTicks - .attr('transform', rotateLabelsRule) - .style('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end'); - } - axisLabel.enter().append('text').attr('class', 'nv-axislabel'); - if (scale.range().length < 2) { - w = 0; - } else if (scale.range().length === 2) { - w = scale.range()[1]; - } else { - w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]); - } - axisLabel - .attr('text-anchor', 'middle') - .attr('y', xLabelMargin) - .attr('x', w/2); - if (showMaxMin) { - //if (showMaxMin && !isOrdinal) { - axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') - //.data(scale.domain()) - .data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]); - axisMaxMin.enter().append('g').attr('class',function(d,i){ - return ['nv-axisMaxMin','nv-axisMaxMin-x',(i == 0 ? 'nv-axisMin-x':'nv-axisMax-x')].join(' ') - }).append('text'); - axisMaxMin.exit().remove(); - axisMaxMin - .attr('transform', function(d,i) { - return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)' - }) - .select('text') - .attr('dy', '.71em') - .attr('y', axis.tickPadding()) - .attr('transform', rotateLabelsRule) - .style('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle') - .text(function(d,i) { - var v = fmt(d); - return ('' + v).match('NaN') ? '' : v; - }); - axisMaxMin.watchTransition(renderWatch, 'min-max bottom') - .attr('transform', function(d,i) { - return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)' - }); - } - if (staggerLabels) - xTicks - .attr('transform', function(d,i) { - return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')' - }); - - break; - case 'right': - axisLabel.enter().append('text').attr('class', 'nv-axislabel'); - axisLabel - .style('text-anchor', rotateYLabel ? 'middle' : 'begin') - .attr('transform', rotateYLabel ? 'rotate(90)' : '') - .attr('y', rotateYLabel ? (-Math.max(margin.right, width) + 12) : -10) //TODO: consider calculating this based on largest tick width... OR at least expose this on chart - .attr('x', rotateYLabel ? (d3.max(scale.range()) / 2) : axis.tickPadding()); - if (showMaxMin) { - axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') - .data(scale.domain()); - axisMaxMin.enter().append('g').attr('class',function(d,i){ - return ['nv-axisMaxMin','nv-axisMaxMin-y',(i == 0 ? 'nv-axisMin-y':'nv-axisMax-y')].join(' ') - }).append('text') - .style('opacity', 0); - axisMaxMin.exit().remove(); - axisMaxMin - .attr('transform', function(d,i) { - return 'translate(0,' + nv.utils.NaNtoZero(scale(d)) + ')' - }) - .select('text') - .attr('dy', '.32em') - .attr('y', 0) - .attr('x', axis.tickPadding()) - .style('text-anchor', 'start') - .text(function(d, i) { - var v = fmt(d); - return ('' + v).match('NaN') ? '' : v; - }); - axisMaxMin.watchTransition(renderWatch, 'min-max right') - .attr('transform', function(d,i) { - return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')' - }) - .select('text') - .style('opacity', 1); - } - break; - case 'left': - /* - //For dynamically placing the label. Can be used with dynamically-sized chart axis margins - var yTicks = g.selectAll('g').select("text"); - yTicks.each(function(d,i){ - var labelPadding = this.getBoundingClientRect().width + axis.tickPadding() + 16; - if(labelPadding > width) width = labelPadding; - }); - */ - axisLabel.enter().append('text').attr('class', 'nv-axislabel'); - axisLabel - .style('text-anchor', rotateYLabel ? 'middle' : 'end') - .attr('transform', rotateYLabel ? 'rotate(-90)' : '') - .attr('y', rotateYLabel ? (-Math.max(margin.left, width) + 25 - (axisLabelDistance || 0)) : -10) - .attr('x', rotateYLabel ? (-d3.max(scale.range()) / 2) : -axis.tickPadding()); - if (showMaxMin) { - axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') - .data(scale.domain()); - axisMaxMin.enter().append('g').attr('class',function(d,i){ - return ['nv-axisMaxMin','nv-axisMaxMin-y',(i == 0 ? 'nv-axisMin-y':'nv-axisMax-y')].join(' ') - }).append('text') - .style('opacity', 0); - axisMaxMin.exit().remove(); - axisMaxMin - .attr('transform', function(d,i) { - return 'translate(0,' + nv.utils.NaNtoZero(scale0(d)) + ')' - }) - .select('text') - .attr('dy', '.32em') - .attr('y', 0) - .attr('x', -axis.tickPadding()) - .attr('text-anchor', 'end') - .text(function(d,i) { - var v = fmt(d); - return ('' + v).match('NaN') ? '' : v; - }); - axisMaxMin.watchTransition(renderWatch, 'min-max right') - .attr('transform', function(d,i) { - return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')' - }) - .select('text') - .style('opacity', 1); - } - break; - } - axisLabel.text(function(d) { return d }); - - if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) { - //check if max and min overlap other values, if so, hide the values that overlap - g.selectAll('g') // the g's wrapping each tick - .each(function(d,i) { - d3.select(this).select('text').attr('opacity', 1); - if (scale(d) < scale.range()[1] + 10 || scale(d) > scale.range()[0] - 10) { // 10 is assuming text height is 16... if d is 0, leave it! - if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL - d3.select(this).attr('opacity', 0); - - d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!! - } - }); - - //if Max and Min = 0 only show min, Issue #281 - if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0) { - wrap.selectAll('g.nv-axisMaxMin').style('opacity', function (d, i) { - return !i ? 1 : 0 - }); - } - } - - if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) { - var maxMinRange = []; - wrap.selectAll('g.nv-axisMaxMin') - .each(function(d,i) { - try { - if (i) // i== 1, max position - maxMinRange.push(scale(d) - this.getBoundingClientRect().width - 4); //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case) - else // i==0, min position - maxMinRange.push(scale(d) + this.getBoundingClientRect().width + 4) - }catch (err) { - if (i) // i== 1, max position - maxMinRange.push(scale(d) - 4); //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case) - else // i==0, min position - maxMinRange.push(scale(d) + 4); - } - }); - // the g's wrapping each tick - g.selectAll('g').each(function(d, i) { - if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) { - if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL - d3.select(this).remove(); - else - d3.select(this).select('text').remove(); // Don't remove the ZERO line!! - } - }); - } - - //Highlight zero tick line - g.selectAll('.tick') - .filter(function (d) { - /* - The filter needs to return only ticks at or near zero. - Numbers like 0.00001 need to count as zero as well, - and the arithmetic trick below solves that. - */ - return !parseFloat(Math.round(d * 100000) / 1000000) && (d !== undefined) - }) - .classed('zero', true); - - //store old scales for use in transitions on update - scale0 = scale.copy(); - - }); - - renderWatch.renderEnd('axis immediate'); - return chart; - } - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - // expose chart's sub-components - chart.axis = axis; - chart.dispatch = dispatch; - - chart.options = nv.utils.optionsFunc.bind(chart); - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - axisLabelDistance: {get: function(){return axisLabelDistance;}, set: function(_){axisLabelDistance=_;}}, - staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}}, - rotateLabels: {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}}, - rotateYLabel: {get: function(){return rotateYLabel;}, set: function(_){rotateYLabel=_;}}, - showMaxMin: {get: function(){return showMaxMin;}, set: function(_){showMaxMin=_;}}, - axisLabel: {get: function(){return axisLabelText;}, set: function(_){axisLabelText=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - ticks: {get: function(){return ticks;}, set: function(_){ticks=_;}}, - width: {get: function(){return width;}, set: function(_){width=_;}}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - duration: {get: function(){return duration;}, set: function(_){ - duration=_; - renderWatch.reset(duration); - }}, - scale: {get: function(){return scale;}, set: function(_){ - scale = _; - axis.scale(scale); - isOrdinal = typeof scale.rangeBands === 'function'; - nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']); - }} - }); - - nv.utils.initOptions(chart); - nv.utils.inheritOptionsD3(chart, axis, ['orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat']); - nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']); - - return chart; - }; - nv.models.boxPlot = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = 960 - , height = 500 - , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one - , x = d3.scale.ordinal() - , y = d3.scale.linear() - , getX = function(d) { return d.x } - , getY = function(d) { return d.y } - , color = nv.utils.defaultColor() - , container = null - , xDomain - , yDomain - , xRange - , yRange - , dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') - , duration = 250 - , maxBoxWidth = null - ; - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - var x0, y0; - var renderWatch = nv.utils.renderWatch(dispatch, duration); - - function chart(selection) { - renderWatch.reset(); - selection.each(function(data) { - var availableWidth = width - margin.left - margin.right, - availableHeight = height - margin.top - margin.bottom; - - container = d3.select(this); - nv.utils.initSVG(container); - - // Setup Scales - x .domain(xDomain || data.map(function(d,i) { return getX(d,i); })) - .rangeBands(xRange || [0, availableWidth], .1); - - // if we know yDomain, no need to calculate - var yData = [] - if (!yDomain) { - // (y-range is based on quartiles, whiskers and outliers) - - // lower values - var yMin = d3.min(data.map(function(d) { - var min_arr = []; - - min_arr.push(d.values.Q1); - if (d.values.hasOwnProperty('whisker_low') && d.values.whisker_low !== null) { min_arr.push(d.values.whisker_low); } - if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { min_arr = min_arr.concat(d.values.outliers); } - - return d3.min(min_arr); - })); - - // upper values - var yMax = d3.max(data.map(function(d) { - var max_arr = []; - - max_arr.push(d.values.Q3); - if (d.values.hasOwnProperty('whisker_high') && d.values.whisker_high !== null) { max_arr.push(d.values.whisker_high); } - if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { max_arr = max_arr.concat(d.values.outliers); } - - return d3.max(max_arr); - })); - - yData = [ yMin, yMax ] ; - } - - y.domain(yDomain || yData); - y.range(yRange || [availableHeight, 0]); - - //store old scales if they exist - x0 = x0 || x; - y0 = y0 || y.copy().range([y(0),y(0)]); - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - var boxplots = wrap.selectAll('.nv-boxplot').data(function(d) { return d }); - var boxEnter = boxplots.enter().append('g').style('stroke-opacity', 1e-6).style('fill-opacity', 1e-6); - boxplots - .attr('class', 'nv-boxplot') - .attr('transform', function(d,i,j) { return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05) + ', 0)'; }) - .classed('hover', function(d) { return d.hover }); - boxplots - .watchTransition(renderWatch, 'nv-boxplot: boxplots') - .style('stroke-opacity', 1) - .style('fill-opacity', .75) - .delay(function(d,i) { return i * duration / data.length }) - .attr('transform', function(d,i) { - return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05) + ', 0)'; - }); - boxplots.exit().remove(); - - // ----- add the SVG elements for each boxPlot ----- - - // conditionally append whisker lines - boxEnter.each(function(d,i) { - var box = d3.select(this); - - ['low', 'high'].forEach(function(key) { - if (d.values.hasOwnProperty('whisker_' + key) && d.values['whisker_' + key] !== null) { - box.append('line') - .style('stroke', (d.color) ? d.color : color(d,i)) - .attr('class', 'nv-boxplot-whisker nv-boxplot-' + key); - - box.append('line') - .style('stroke', (d.color) ? d.color : color(d,i)) - .attr('class', 'nv-boxplot-tick nv-boxplot-' + key); - } - }); - }); - - // outliers - // TODO: support custom colors here - var outliers = boxplots.selectAll('.nv-boxplot-outlier').data(function(d) { - if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { return d.values.outliers; } - else { return []; } - }); - outliers.enter().append('circle') - .style('fill', function(d,i,j) { return color(d,j) }).style('stroke', function(d,i,j) { return color(d,j) }) - .on('mouseover', function(d,i,j) { - d3.select(this).classed('hover', true); - dispatch.elementMouseover({ - series: { key: d, color: color(d,j) }, - e: d3.event - }); - }) - .on('mouseout', function(d,i,j) { - d3.select(this).classed('hover', false); - dispatch.elementMouseout({ - series: { key: d, color: color(d,j) }, - e: d3.event - }); - }) - .on('mousemove', function(d,i) { - dispatch.elementMousemove({e: d3.event}); - }); - - outliers.attr('class', 'nv-boxplot-outlier'); - outliers - .watchTransition(renderWatch, 'nv-boxplot: nv-boxplot-outlier') - .attr('cx', x.rangeBand() * .45) - .attr('cy', function(d,i,j) { return y(d); }) - .attr('r', '3'); - outliers.exit().remove(); - - var box_width = function() { return (maxBoxWidth === null ? x.rangeBand() * .9 : Math.min(75, x.rangeBand() * .9)); }; - var box_left = function() { return x.rangeBand() * .45 - box_width()/2; }; - var box_right = function() { return x.rangeBand() * .45 + box_width()/2; }; - - // update whisker lines and ticks - ['low', 'high'].forEach(function(key) { - var endpoint = (key === 'low') ? 'Q1' : 'Q3'; - - boxplots.select('line.nv-boxplot-whisker.nv-boxplot-' + key) - .watchTransition(renderWatch, 'nv-boxplot: boxplots') - .attr('x1', x.rangeBand() * .45 ) - .attr('y1', function(d,i) { return y(d.values['whisker_' + key]); }) - .attr('x2', x.rangeBand() * .45 ) - .attr('y2', function(d,i) { return y(d.values[endpoint]); }); - - boxplots.select('line.nv-boxplot-tick.nv-boxplot-' + key) - .watchTransition(renderWatch, 'nv-boxplot: boxplots') - .attr('x1', box_left ) - .attr('y1', function(d,i) { return y(d.values['whisker_' + key]); }) - .attr('x2', box_right ) - .attr('y2', function(d,i) { return y(d.values['whisker_' + key]); }); - }); - - ['low', 'high'].forEach(function(key) { - boxEnter.selectAll('.nv-boxplot-' + key) - .on('mouseover', function(d,i,j) { - d3.select(this).classed('hover', true); - dispatch.elementMouseover({ - series: { key: d.values['whisker_' + key], color: color(d,j) }, - e: d3.event - }); - }) - .on('mouseout', function(d,i,j) { - d3.select(this).classed('hover', false); - dispatch.elementMouseout({ - series: { key: d.values['whisker_' + key], color: color(d,j) }, - e: d3.event - }); - }) - .on('mousemove', function(d,i) { - dispatch.elementMousemove({e: d3.event}); - }); - }); - - // boxes - boxEnter.append('rect') - .attr('class', 'nv-boxplot-box') - // tooltip events - .on('mouseover', function(d,i) { - d3.select(this).classed('hover', true); - dispatch.elementMouseover({ - key: d.label, - value: d.label, - series: [ - { key: 'Q3', value: d.values.Q3, color: d.color || color(d,i) }, - { key: 'Q2', value: d.values.Q2, color: d.color || color(d,i) }, - { key: 'Q1', value: d.values.Q1, color: d.color || color(d,i) } - ], - data: d, - index: i, - e: d3.event - }); - }) - .on('mouseout', function(d,i) { - d3.select(this).classed('hover', false); - dispatch.elementMouseout({ - key: d.label, - value: d.label, - series: [ - { key: 'Q3', value: d.values.Q3, color: d.color || color(d,i) }, - { key: 'Q2', value: d.values.Q2, color: d.color || color(d,i) }, - { key: 'Q1', value: d.values.Q1, color: d.color || color(d,i) } - ], - data: d, - index: i, - e: d3.event - }); - }) - .on('mousemove', function(d,i) { - dispatch.elementMousemove({e: d3.event}); - }); - - // box transitions - boxplots.select('rect.nv-boxplot-box') - .watchTransition(renderWatch, 'nv-boxplot: boxes') - .attr('y', function(d,i) { return y(d.values.Q3); }) - .attr('width', box_width) - .attr('x', box_left ) - - .attr('height', function(d,i) { return Math.abs(y(d.values.Q3) - y(d.values.Q1)) || 1 }) - .style('fill', function(d,i) { return d.color || color(d,i) }) - .style('stroke', function(d,i) { return d.color || color(d,i) }); - - // median line - boxEnter.append('line').attr('class', 'nv-boxplot-median'); - - boxplots.select('line.nv-boxplot-median') - .watchTransition(renderWatch, 'nv-boxplot: boxplots line') - .attr('x1', box_left) - .attr('y1', function(d,i) { return y(d.values.Q2); }) - .attr('x2', box_right) - .attr('y2', function(d,i) { return y(d.values.Q2); }); - - //store old scales for use in transitions on update - x0 = x.copy(); - y0 = y.copy(); - }); - - renderWatch.renderEnd('nv-boxplot immediate'); - return chart; - } - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - maxBoxWidth: {get: function(){return maxBoxWidth;}, set: function(_){maxBoxWidth=_;}}, - x: {get: function(){return getX;}, set: function(_){getX=_;}}, - y: {get: function(){return getY;}, set: function(_){getY=_;}}, - xScale: {get: function(){return x;}, set: function(_){x=_;}}, - yScale: {get: function(){return y;}, set: function(_){y=_;}}, - xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, - yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, - xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, - yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, - id: {get: function(){return id;}, set: function(_){id=_;}}, - // rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - }}, - duration: {get: function(){return duration;}, set: function(_){ - duration = _; - renderWatch.reset(duration); - }} - }); - - nv.utils.initOptions(chart); - - return chart; - }; - nv.models.boxPlotChart = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var boxplot = nv.models.boxPlot() - , xAxis = nv.models.axis() - , yAxis = nv.models.axis() - ; - - var margin = {top: 15, right: 10, bottom: 50, left: 60} - , width = null - , height = null - , color = nv.utils.getColor() - , showXAxis = true - , showYAxis = true - , rightAlignYAxis = false - , staggerLabels = false - , tooltip = nv.models.tooltip() - , x - , y - , noData = "No Data Available." - , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'beforeUpdate', 'renderEnd') - , duration = 250 - ; - - xAxis - .orient('bottom') - .showMaxMin(false) - .tickFormat(function(d) { return d }) - ; - yAxis - .orient((rightAlignYAxis) ? 'right' : 'left') - .tickFormat(d3.format(',.1f')) - ; - - tooltip.duration(0); - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - var renderWatch = nv.utils.renderWatch(dispatch, duration); - - function chart(selection) { - renderWatch.reset(); - renderWatch.models(boxplot); - if (showXAxis) renderWatch.models(xAxis); - if (showYAxis) renderWatch.models(yAxis); - - selection.each(function(data) { - var container = d3.select(this), - that = this; - nv.utils.initSVG(container); - var availableWidth = (width || parseInt(container.style('width')) || 960) - - margin.left - margin.right, - availableHeight = (height || parseInt(container.style('height')) || 400) - - margin.top - margin.bottom; - - chart.update = function() { - dispatch.beforeUpdate(); - container.transition().duration(duration).call(chart); - }; - chart.container = this; - - // Display No Data message if there's nothing to show. (quartiles required at minimum) - if (!data || !data.length || - !data.filter(function(d) { return d.values.hasOwnProperty("Q1") && d.values.hasOwnProperty("Q2") && d.values.hasOwnProperty("Q3"); }).length) { - var noDataText = container.selectAll('.nv-noData').data([noData]); - - noDataText.enter().append('text') - .attr('class', 'nvd3 nv-noData') - .attr('dy', '-.7em') - .style('text-anchor', 'middle'); - - noDataText - .attr('x', margin.left + availableWidth / 2) - .attr('y', margin.top + availableHeight / 2) - .text(function(d) { return d }); - - return chart; - } else { - container.selectAll('.nv-noData').remove(); - } - - // Setup Scales - x = boxplot.xScale(); - y = boxplot.yScale().clamp(true); - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-boxPlotWithAxes').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-boxPlotWithAxes').append('g'); - var defsEnter = gEnter.append('defs'); - var g = wrap.select('g'); - - gEnter.append('g').attr('class', 'nv-x nv-axis'); - gEnter.append('g').attr('class', 'nv-y nv-axis') - .append('g').attr('class', 'nv-zeroLine') - .append('line'); - - gEnter.append('g').attr('class', 'nv-barsWrap'); - - g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - if (rightAlignYAxis) { - g.select(".nv-y.nv-axis") - .attr("transform", "translate(" + availableWidth + ",0)"); - } - - // Main Chart Component(s) - boxplot - .width(availableWidth) - .height(availableHeight); - - var barsWrap = g.select('.nv-barsWrap') - .datum(data.filter(function(d) { return !d.disabled })) - - barsWrap.transition().call(boxplot); - - - defsEnter.append('clipPath') - .attr('id', 'nv-x-label-clip-' + boxplot.id()) - .append('rect'); - - g.select('#nv-x-label-clip-' + boxplot.id() + ' rect') - .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1)) - .attr('height', 16) - .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 )); - - // Setup Axes - if (showXAxis) { - xAxis - .scale(x) - .ticks( nv.utils.calcTicksX(availableWidth/100, data) ) - .tickSize(-availableHeight, 0); - - g.select('.nv-x.nv-axis').attr('transform', 'translate(0,' + y.range()[0] + ')'); - g.select('.nv-x.nv-axis').call(xAxis); - - var xTicks = g.select('.nv-x.nv-axis').selectAll('g'); - if (staggerLabels) { - xTicks - .selectAll('text') - .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' }) - } - } - - if (showYAxis) { - yAxis - .scale(y) - .ticks( Math.floor(availableHeight/36) ) // can't use nv.utils.calcTicksY with Object data - .tickSize( -availableWidth, 0); - - g.select('.nv-y.nv-axis').call(yAxis); - } - - // Zero line - g.select(".nv-zeroLine line") - .attr("x1",0) - .attr("x2",availableWidth) - .attr("y1", y(0)) - .attr("y2", y(0)) - ; - - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ - }); - - renderWatch.renderEnd('nv-boxplot chart immediate'); - return chart; - } - - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ - - boxplot.dispatch.on('elementMouseover.tooltip', function(evt) { - tooltip.data(evt).hidden(false); - }); - - boxplot.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.data(evt).hidden(true); - }); - - boxplot.dispatch.on('elementMousemove.tooltip', function(evt) { - tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); - }); - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - chart.dispatch = dispatch; - chart.boxplot = boxplot; - chart.xAxis = xAxis; - chart.yAxis = yAxis; - chart.tooltip = tooltip; - - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}}, - showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, - showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, - tooltips: {get: function(){return tooltips;}, set: function(_){tooltips=_;}}, - tooltipContent: {get: function(){return tooltip;}, set: function(_){tooltip=_;}}, - noData: {get: function(){return noData;}, set: function(_){noData=_;}}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - duration: {get: function(){return duration;}, set: function(_){ - duration = _; - renderWatch.reset(duration); - boxplot.duration(duration); - xAxis.duration(duration); - yAxis.duration(duration); - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - boxplot.color(color); - }}, - rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ - rightAlignYAxis = _; - yAxis.orient( (_) ? 'right' : 'left'); - }} - }); - - nv.utils.inheritOptions(chart, boxplot); - nv.utils.initOptions(chart); - - return chart; - } -// Chart design based on the recommendations of Stephen Few. Implementation -// based on the work of Clint Ivy, Jamie Love, and Jason Davies. -// http://projects.instantcognition.com/protovis/bulletchart/ - - nv.models.bullet = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , orient = 'left' // TODO top & bottom - , reverse = false - , ranges = function(d) { return d.ranges } - , markers = function(d) { return d.markers ? d.markers : [0] } - , measures = function(d) { return d.measures } - , rangeLabels = function(d) { return d.rangeLabels ? d.rangeLabels : [] } - , markerLabels = function(d) { return d.markerLabels ? d.markerLabels : [] } - , measureLabels = function(d) { return d.measureLabels ? d.measureLabels : [] } - , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.) - , width = 380 - , height = 30 - , container = null - , tickFormat = null - , color = nv.utils.getColor(['#1f77b4']) - , dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove') - ; - - function chart(selection) { - selection.each(function(d, i) { - var availableWidth = width - margin.left - margin.right, - availableHeight = height - margin.top - margin.bottom; - - container = d3.select(this); - nv.utils.initSVG(container); - - var rangez = ranges.call(this, d, i).slice().sort(d3.descending), - markerz = markers.call(this, d, i).slice().sort(d3.descending), - measurez = measures.call(this, d, i).slice().sort(d3.descending), - rangeLabelz = rangeLabels.call(this, d, i).slice(), - markerLabelz = markerLabels.call(this, d, i).slice(), - measureLabelz = measureLabels.call(this, d, i).slice(); - - // Setup Scales - // Compute the new x-scale. - var x1 = d3.scale.linear() - .domain( d3.extent(d3.merge([forceX, rangez])) ) - .range(reverse ? [availableWidth, 0] : [0, availableWidth]); - - // Retrieve the old x-scale, if this is an update. - var x0 = this.__chart__ || d3.scale.linear() - .domain([0, Infinity]) - .range(x1.range()); - - // Stash the new scale. - this.__chart__ = x1; - - var rangeMin = d3.min(rangez), //rangez[2] - rangeMax = d3.max(rangez), //rangez[0] - rangeAvg = rangez[1]; - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); - - gEnter.append('rect').attr('class', 'nv-range nv-rangeMax'); - gEnter.append('rect').attr('class', 'nv-range nv-rangeAvg'); - gEnter.append('rect').attr('class', 'nv-range nv-rangeMin'); - gEnter.append('rect').attr('class', 'nv-measure'); - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0) - w1 = function(d) { return Math.abs(x1(d) - x1(0)) }; - var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) }, - xp1 = function(d) { return d < 0 ? x1(d) : x1(0) }; - - g.select('rect.nv-rangeMax') - .attr('height', availableHeight) - .attr('width', w1(rangeMax > 0 ? rangeMax : rangeMin)) - .attr('x', xp1(rangeMax > 0 ? rangeMax : rangeMin)) - .datum(rangeMax > 0 ? rangeMax : rangeMin) - - g.select('rect.nv-rangeAvg') - .attr('height', availableHeight) - .attr('width', w1(rangeAvg)) - .attr('x', xp1(rangeAvg)) - .datum(rangeAvg) - - g.select('rect.nv-rangeMin') - .attr('height', availableHeight) - .attr('width', w1(rangeMax)) - .attr('x', xp1(rangeMax)) - .attr('width', w1(rangeMax > 0 ? rangeMin : rangeMax)) - .attr('x', xp1(rangeMax > 0 ? rangeMin : rangeMax)) - .datum(rangeMax > 0 ? rangeMin : rangeMax) - - g.select('rect.nv-measure') - .style('fill', color) - .attr('height', availableHeight / 3) - .attr('y', availableHeight / 3) - .attr('width', measurez < 0 ? - x1(0) - x1(measurez[0]) - : x1(measurez[0]) - x1(0)) - .attr('x', xp1(measurez)) - .on('mouseover', function() { - dispatch.elementMouseover({ - value: measurez[0], - label: measureLabelz[0] || 'Current', - color: d3.select(this).style("fill") - }) - }) - .on('mousemove', function() { - dispatch.elementMousemove({ - value: measurez[0], - label: measureLabelz[0] || 'Current', - color: d3.select(this).style("fill") - }) - }) - .on('mouseout', function() { - dispatch.elementMouseout({ - value: measurez[0], - label: measureLabelz[0] || 'Current', - color: d3.select(this).style("fill") - }) - }); - - var h3 = availableHeight / 6; - - var markerData = markerz.map( function(marker, index) { - return {value: marker, label: markerLabelz[index]} - }); - gEnter - .selectAll("path.nv-markerTriangle") - .data(markerData) - .enter() - .append('path') - .attr('class', 'nv-markerTriangle') - .attr('transform', function(d) { return 'translate(' + x1(d.value) + ',' + (availableHeight / 2) + ')' }) - .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z') - .on('mouseover', function(d) { - dispatch.elementMouseover({ - value: d.value, - label: d.label || 'Previous', - color: d3.select(this).style("fill"), - pos: [x1(d.value), availableHeight/2] - }) - - }) - .on('mousemove', function(d) { - dispatch.elementMousemove({ - value: d.value, - label: d.label || 'Previous', - color: d3.select(this).style("fill") - }) - }) - .on('mouseout', function(d, i) { - dispatch.elementMouseout({ - value: d.value, - label: d.label || 'Previous', - color: d3.select(this).style("fill") - }) - }); - - wrap.selectAll('.nv-range') - .on('mouseover', function(d,i) { - var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum"); - dispatch.elementMouseover({ - value: d, - label: label, - color: d3.select(this).style("fill") - }) - }) - .on('mousemove', function() { - dispatch.elementMousemove({ - value: measurez[0], - label: measureLabelz[0] || 'Previous', - color: d3.select(this).style("fill") - }) - }) - .on('mouseout', function(d,i) { - var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum"); - dispatch.elementMouseout({ - value: d, - label: label, - color: d3.select(this).style("fill") - }) - }); - }); - - return chart; - } - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - ranges: {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good) - markers: {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal) - measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast) - forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}}, - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - tickFormat: {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom - orient = _; - reverse = orient == 'right' || orient == 'bottom'; - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - }} - }); - - nv.utils.initOptions(chart); - return chart; - }; - - - -// Chart design based on the recommendations of Stephen Few. Implementation -// based on the work of Clint Ivy, Jamie Love, and Jason Davies. -// http://projects.instantcognition.com/protovis/bulletchart/ - nv.models.bulletChart = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var bullet = nv.models.bullet(); - var tooltip = nv.models.tooltip(); - - var orient = 'left' // TODO top & bottom - , reverse = false - , margin = {top: 5, right: 40, bottom: 20, left: 120} - , ranges = function(d) { return d.ranges } - , markers = function(d) { return d.markers ? d.markers : [0] } - , measures = function(d) { return d.measures } - , width = null - , height = 55 - , tickFormat = null - , ticks = null - , noData = null - , dispatch = d3.dispatch('tooltipShow', 'tooltipHide') - ; - - tooltip.duration(0).headerEnabled(false); - - function chart(selection) { - selection.each(function(d, i) { - var container = d3.select(this); - nv.utils.initSVG(container); - - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = height - margin.top - margin.bottom, - that = this; - - chart.update = function() { chart(selection) }; - chart.container = this; - - // Display No Data message if there's nothing to show. - if (!d || !ranges.call(this, d, i)) { - nv.utils.noData(chart, container) - return chart; - } else { - container.selectAll('.nv-noData').remove(); - } - - var rangez = ranges.call(this, d, i).slice().sort(d3.descending), - markerz = markers.call(this, d, i).slice().sort(d3.descending), - measurez = measures.call(this, d, i).slice().sort(d3.descending); - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); - - gEnter.append('g').attr('class', 'nv-bulletWrap'); - gEnter.append('g').attr('class', 'nv-titles'); - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - // Compute the new x-scale. - var x1 = d3.scale.linear() - .domain([0, Math.max(rangez[0], markerz[0], measurez[0])]) // TODO: need to allow forceX and forceY, and xDomain, yDomain - .range(reverse ? [availableWidth, 0] : [0, availableWidth]); - - // Retrieve the old x-scale, if this is an update. - var x0 = this.__chart__ || d3.scale.linear() - .domain([0, Infinity]) - .range(x1.range()); - - // Stash the new scale. - this.__chart__ = x1; - - var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0) - w1 = function(d) { return Math.abs(x1(d) - x1(0)) }; - - var title = gEnter.select('.nv-titles').append('g') - .attr('text-anchor', 'end') - .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')'); - title.append('text') - .attr('class', 'nv-title') - .text(function(d) { return d.title; }); - - title.append('text') - .attr('class', 'nv-subtitle') - .attr('dy', '1em') - .text(function(d) { return d.subtitle; }); - - bullet - .width(availableWidth) - .height(availableHeight) - - var bulletWrap = g.select('.nv-bulletWrap'); - d3.transition(bulletWrap).call(bullet); - - // Compute the tick format. - var format = tickFormat || x1.tickFormat( availableWidth / 100 ); - - // Update the tick groups. - var tick = g.selectAll('g.nv-tick') - .data(x1.ticks( ticks ? ticks : (availableWidth / 50) ), function(d) { - return this.textContent || format(d); - }); - - // Initialize the ticks with the old scale, x0. - var tickEnter = tick.enter().append('g') - .attr('class', 'nv-tick') - .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' }) - .style('opacity', 1e-6); - - tickEnter.append('line') - .attr('y1', availableHeight) - .attr('y2', availableHeight * 7 / 6); - - tickEnter.append('text') - .attr('text-anchor', 'middle') - .attr('dy', '1em') - .attr('y', availableHeight * 7 / 6) - .text(format); - - // Transition the updating ticks to the new scale, x1. - var tickUpdate = d3.transition(tick) - .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' }) - .style('opacity', 1); - - tickUpdate.select('line') - .attr('y1', availableHeight) - .attr('y2', availableHeight * 7 / 6); - - tickUpdate.select('text') - .attr('y', availableHeight * 7 / 6); - - // Transition the exiting ticks to the new scale, x1. - d3.transition(tick.exit()) - .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' }) - .style('opacity', 1e-6) - .remove(); - }); - - d3.timer.flush(); - return chart; - } - - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ - - bullet.dispatch.on('elementMouseover.tooltip', function(evt) { - evt['series'] = { - key: evt.label, - value: evt.value, - color: evt.color - }; - tooltip.data(evt).hidden(false); - }); - - bullet.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true); - }); - - bullet.dispatch.on('elementMousemove.tooltip', function(evt) { - tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); - }); - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - chart.bullet = bullet; - chart.dispatch = dispatch; - chart.tooltip = tooltip; - - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - ranges: {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good) - markers: {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal) - measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast) - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - tickFormat: {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}}, - ticks: {get: function(){return ticks;}, set: function(_){ticks=_;}}, - noData: {get: function(){return noData;}, set: function(_){noData=_;}}, - - // deprecated options - tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); - tooltip.enabled(!!_); - }}, - tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); - tooltip.contentGenerator(_); - }}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom - orient = _; - reverse = orient == 'right' || orient == 'bottom'; - }} - }); - - nv.utils.inheritOptions(chart, bullet); - nv.utils.initOptions(chart); - - return chart; - }; - - - - nv.models.candlestickBar = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = null - , height = null - , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one - , container - , x = d3.scale.linear() - , y = d3.scale.linear() - , getX = function(d) { return d.x } - , getY = function(d) { return d.y } - , getOpen = function(d) { return d.open } - , getClose = function(d) { return d.close } - , getHigh = function(d) { return d.high } - , getLow = function(d) { return d.low } - , forceX = [] - , forceY = [] - , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart - , clipEdge = true - , color = nv.utils.defaultColor() - , interactive = false - , xDomain - , yDomain - , xRange - , yRange - , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove') - ; - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - function chart(selection) { - selection.each(function(data) { - container = d3.select(this); - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); - - nv.utils.initSVG(container); - - // Width of the candlestick bars. - var barWidth = (availableWidth / data[0].values.length) * .45; - - // Setup Scales - x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) )); - - if (padData) - x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); - else - x.range(xRange || [5 + barWidth / 2, availableWidth - barWidth / 2 - 5]); - - y.domain(yDomain || [ - d3.min(data[0].values.map(getLow).concat(forceY)), - d3.max(data[0].values.map(getHigh).concat(forceY)) - ] - ).range(yRange || [availableHeight, 0]); - - // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point - if (x.domain()[0] === x.domain()[1]) - x.domain()[0] ? - x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) - : x.domain([-1,1]); - - if (y.domain()[0] === y.domain()[1]) - y.domain()[0] ? - y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) - : y.domain([-1,1]); - - // Setup containers and skeleton of chart - var wrap = d3.select(this).selectAll('g.nv-wrap.nv-candlestickBar').data([data[0].values]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-candlestickBar'); - var defsEnter = wrapEnter.append('defs'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); - - gEnter.append('g').attr('class', 'nv-ticks'); - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - container - .on('click', function(d,i) { - dispatch.chartClick({ - data: d, - index: i, - pos: d3.event, - id: id - }); - }); - - defsEnter.append('clipPath') - .attr('id', 'nv-chart-clip-path-' + id) - .append('rect'); - - wrap.select('#nv-chart-clip-path-' + id + ' rect') - .attr('width', availableWidth) - .attr('height', availableHeight); - - g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : ''); - - var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick') - .data(function(d) { return d }); - ticks.exit().remove(); - - // The colors are currently controlled by CSS. - var tickGroups = ticks.enter().append('g') - .attr('class', function(d, i, j) { return (getOpen(d, i) > getClose(d, i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i}); - - var lines = tickGroups.append('line') - .attr('class', 'nv-candlestick-lines') - .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; }) - .attr('x1', 0) - .attr('y1', function(d, i) { return y(getHigh(d, i)); }) - .attr('x2', 0) - .attr('y2', function(d, i) { return y(getLow(d, i)); }); - - var rects = tickGroups.append('rect') - .attr('class', 'nv-candlestick-rects nv-bars') - .attr('transform', function(d, i) { - return 'translate(' + (x(getX(d, i)) - barWidth/2) + ',' - + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0)) - + ')'; - }) - .attr('x', 0) - .attr('y', 0) - .attr('width', barWidth) - .attr('height', function(d, i) { - var open = getOpen(d, i); - var close = getClose(d, i); - return open > close ? y(close) - y(open) : y(open) - y(close); - }); - - container.selectAll('.nv-candlestick-lines').transition() - .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; }) - .attr('x1', 0) - .attr('y1', function(d, i) { return y(getHigh(d, i)); }) - .attr('x2', 0) - .attr('y2', function(d, i) { return y(getLow(d, i)); }); - - container.selectAll('.nv-candlestick-rects').transition() - .attr('transform', function(d, i) { - return 'translate(' + (x(getX(d, i)) - barWidth/2) + ',' - + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0)) - + ')'; - }) - .attr('x', 0) - .attr('y', 0) - .attr('width', barWidth) - .attr('height', function(d, i) { - var open = getOpen(d, i); - var close = getClose(d, i); - return open > close ? y(close) - y(open) : y(open) - y(close); - }); - }); - - return chart; - } - - - //Create methods to allow outside functions to highlight a specific bar. - chart.highlightPoint = function(pointIndex, isHoverOver) { - chart.clearHighlights(); - container.select(".nv-candlestickBar .nv-tick-0-" + pointIndex) - .classed("hover", isHoverOver) - ; - }; - - chart.clearHighlights = function() { - container.select(".nv-candlestickBar .nv-tick.hover") - .classed("hover", false) - ; - }; - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - xScale: {get: function(){return x;}, set: function(_){x=_;}}, - yScale: {get: function(){return y;}, set: function(_){y=_;}}, - xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, - yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, - xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, - yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, - forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}}, - forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, - padData: {get: function(){return padData;}, set: function(_){padData=_;}}, - clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, - id: {get: function(){return id;}, set: function(_){id=_;}}, - interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}}, - - x: {get: function(){return getX;}, set: function(_){getX=_;}}, - y: {get: function(){return getY;}, set: function(_){getY=_;}}, - open: {get: function(){return getOpen();}, set: function(_){getOpen=_;}}, - close: {get: function(){return getClose();}, set: function(_){getClose=_;}}, - high: {get: function(){return getHigh;}, set: function(_){getHigh=_;}}, - low: {get: function(){return getLow;}, set: function(_){getLow=_;}}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top != undefined ? _.top : margin.top; - margin.right = _.right != undefined ? _.right : margin.right; - margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom; - margin.left = _.left != undefined ? _.left : margin.left; - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - }} - }); - - nv.utils.initOptions(chart); - return chart; - }; - - nv.models.cumulativeLineChart = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var lines = nv.models.line() - , xAxis = nv.models.axis() - , yAxis = nv.models.axis() - , legend = nv.models.legend() - , controls = nv.models.legend() - , interactiveLayer = nv.interactiveGuideline() - , tooltip = nv.models.tooltip() - ; - - var margin = {top: 30, right: 30, bottom: 50, left: 60} - , color = nv.utils.defaultColor() - , width = null - , height = null - , showLegend = true - , showXAxis = true - , showYAxis = true - , rightAlignYAxis = false - , showControls = true - , useInteractiveGuideline = false - , rescaleY = true - , x //can be accessed via chart.xScale() - , y //can be accessed via chart.yScale() - , id = lines.id() - , state = nv.utils.state() - , defaultState = null - , noData = null - , average = function(d) { return d.average } - , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd') - , transitionDuration = 250 - , duration = 250 - , noErrorCheck = false //if set to TRUE, will bypass an error check in the indexify function. - ; - - state.index = 0; - state.rescaleY = rescaleY; - - xAxis.orient('bottom').tickPadding(7); - yAxis.orient((rightAlignYAxis) ? 'right' : 'left'); - - tooltip.valueFormatter(function(d, i) { - return yAxis.tickFormat()(d, i); - }).headerFormatter(function(d, i) { - return xAxis.tickFormat()(d, i); - }); - - controls.updateState(false); - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - var dx = d3.scale.linear() - , index = {i: 0, x: 0} - , renderWatch = nv.utils.renderWatch(dispatch, duration) - ; - - var stateGetter = function(data) { - return function(){ - return { - active: data.map(function(d) { return !d.disabled }), - index: index.i, - rescaleY: rescaleY - }; - } - }; - - var stateSetter = function(data) { - return function(state) { - if (state.index !== undefined) - index.i = state.index; - if (state.rescaleY !== undefined) - rescaleY = state.rescaleY; - if (state.active !== undefined) - data.forEach(function(series,i) { - series.disabled = !state.active[i]; - }); - } - }; - - function chart(selection) { - renderWatch.reset(); - renderWatch.models(lines); - if (showXAxis) renderWatch.models(xAxis); - if (showYAxis) renderWatch.models(yAxis); - selection.each(function(data) { - var container = d3.select(this); - nv.utils.initSVG(container); - container.classed('nv-chart-' + id, true); - var that = this; - - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); - - chart.update = function() { - if (duration === 0) - container.call(chart); - else - container.transition().duration(duration).call(chart) - }; - chart.container = this; - - state - .setter(stateSetter(data), chart.update) - .getter(stateGetter(data)) - .update(); - - // DEPRECATED set state.disableddisabled - state.disabled = data.map(function(d) { return !!d.disabled }); - - if (!defaultState) { - var key; - defaultState = {}; - for (key in state) { - if (state[key] instanceof Array) - defaultState[key] = state[key].slice(0); - else - defaultState[key] = state[key]; - } - } - - var indexDrag = d3.behavior.drag() - .on('dragstart', dragStart) - .on('drag', dragMove) - .on('dragend', dragEnd); - - - function dragStart(d,i) { - d3.select(chart.container) - .style('cursor', 'ew-resize'); - } - - function dragMove(d,i) { - index.x = d3.event.x; - index.i = Math.round(dx.invert(index.x)); - updateZero(); - } - - function dragEnd(d,i) { - d3.select(chart.container) - .style('cursor', 'auto'); - - // update state and send stateChange with new index - state.index = index.i; - dispatch.stateChange(state); - } - - // Display No Data message if there's nothing to show. - if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { - nv.utils.noData(chart, container) - return chart; - } else { - container.selectAll('.nv-noData').remove(); - } - - // Setup Scales - x = lines.xScale(); - y = lines.yScale(); - - if (!rescaleY) { - var seriesDomains = data - .filter(function(series) { return !series.disabled }) - .map(function(series,i) { - var initialDomain = d3.extent(series.values, lines.y()); - - //account for series being disabled when losing 95% or more - if (initialDomain[0] < -.95) initialDomain[0] = -.95; - - return [ - (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]), - (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0]) - ]; - }); - - var completeDomain = [ - d3.min(seriesDomains, function(d) { return d[0] }), - d3.max(seriesDomains, function(d) { return d[1] }) - ]; - - lines.yDomain(completeDomain); - } else { - lines.yDomain(null); - } - - dx.domain([0, data[0].values.length - 1]) //Assumes all series have same length - .range([0, availableWidth]) - .clamp(true); - - var data = indexify(index.i, data); - - // Setup containers and skeleton of chart - var interactivePointerEvents = (useInteractiveGuideline) ? "none" : "all"; - var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g'); - var g = wrap.select('g'); - - gEnter.append('g').attr('class', 'nv-interactive'); - gEnter.append('g').attr('class', 'nv-x nv-axis').style("pointer-events","none"); - gEnter.append('g').attr('class', 'nv-y nv-axis'); - gEnter.append('g').attr('class', 'nv-background'); - gEnter.append('g').attr('class', 'nv-linesWrap').style("pointer-events",interactivePointerEvents); - gEnter.append('g').attr('class', 'nv-avgLinesWrap').style("pointer-events","none"); - gEnter.append('g').attr('class', 'nv-legendWrap'); - gEnter.append('g').attr('class', 'nv-controlsWrap'); - - // Legend - if (showLegend) { - legend.width(availableWidth); - - g.select('.nv-legendWrap') - .datum(data) - .call(legend); - - if ( margin.top != legend.height()) { - margin.top = legend.height(); - availableHeight = nv.utils.availableHeight(height, container, margin); - } - - g.select('.nv-legendWrap') - .attr('transform', 'translate(0,' + (-margin.top) +')') - } - - // Controls - if (showControls) { - var controlsData = [ - { key: 'Re-scale y-axis', disabled: !rescaleY } - ]; - - controls - .width(140) - .color(['#444', '#444', '#444']) - .rightAlign(false) - .margin({top: 5, right: 0, bottom: 5, left: 20}) - ; - - g.select('.nv-controlsWrap') - .datum(controlsData) - .attr('transform', 'translate(0,' + (-margin.top) +')') - .call(controls); - } - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - if (rightAlignYAxis) { - g.select(".nv-y.nv-axis") - .attr("transform", "translate(" + availableWidth + ",0)"); - } - - // Show error if series goes below 100% - var tempDisabled = data.filter(function(d) { return d.tempDisabled }); - - wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates - if (tempDisabled.length) { - wrap.append('text').attr('class', 'tempDisabled') - .attr('x', availableWidth / 2) - .attr('y', '-.71em') - .style('text-anchor', 'end') - .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.'); - } - - //Set up interactive layer - if (useInteractiveGuideline) { - interactiveLayer - .width(availableWidth) - .height(availableHeight) - .margin({left:margin.left,top:margin.top}) - .svgContainer(container) - .xScale(x); - wrap.select(".nv-interactive").call(interactiveLayer); - } - - gEnter.select('.nv-background') - .append('rect'); - - g.select('.nv-background rect') - .attr('width', availableWidth) - .attr('height', availableHeight); - - lines - //.x(function(d) { return d.x }) - .y(function(d) { return d.display.y }) - .width(availableWidth) - .height(availableHeight) - .color(data.map(function(d,i) { - return d.color || color(d, i); - }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled; })); - - var linesWrap = g.select('.nv-linesWrap') - .datum(data.filter(function(d) { return !d.disabled && !d.tempDisabled })); - - linesWrap.call(lines); - - //Store a series index number in the data array. - data.forEach(function(d,i) { - d.seriesIndex = i; - }); - - var avgLineData = data.filter(function(d) { - return !d.disabled && !!average(d); - }); - - var avgLines = g.select(".nv-avgLinesWrap").selectAll("line") - .data(avgLineData, function(d) { return d.key; }); - - var getAvgLineY = function(d) { - //If average lines go off the svg element, clamp them to the svg bounds. - var yVal = y(average(d)); - if (yVal < 0) return 0; - if (yVal > availableHeight) return availableHeight; - return yVal; - }; - - avgLines.enter() - .append('line') - .style('stroke-width',2) - .style('stroke-dasharray','10,10') - .style('stroke',function (d,i) { - return lines.color()(d,d.seriesIndex); - }) - .attr('x1',0) - .attr('x2',availableWidth) - .attr('y1', getAvgLineY) - .attr('y2', getAvgLineY); - - avgLines - .style('stroke-opacity',function(d){ - //If average lines go offscreen, make them transparent - var yVal = y(average(d)); - if (yVal < 0 || yVal > availableHeight) return 0; - return 1; - }) - .attr('x1',0) - .attr('x2',availableWidth) - .attr('y1', getAvgLineY) - .attr('y2', getAvgLineY); - - avgLines.exit().remove(); - - //Create index line - var indexLine = linesWrap.selectAll('.nv-indexLine') - .data([index]); - indexLine.enter().append('rect').attr('class', 'nv-indexLine') - .attr('width', 3) - .attr('x', -2) - .attr('fill', 'red') - .attr('fill-opacity', .5) - .style("pointer-events","all") - .call(indexDrag); - - indexLine - .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' }) - .attr('height', availableHeight); - - // Setup Axes - if (showXAxis) { - xAxis - .scale(x) - ._ticks( nv.utils.calcTicksX(availableWidth/70, data) ) - .tickSize(-availableHeight, 0); - - g.select('.nv-x.nv-axis') - .attr('transform', 'translate(0,' + y.range()[0] + ')'); - g.select('.nv-x.nv-axis') - .call(xAxis); - } - - if (showYAxis) { - yAxis - .scale(y) - ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) - .tickSize( -availableWidth, 0); - - g.select('.nv-y.nv-axis') - .call(yAxis); - } - - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ - - function updateZero() { - indexLine - .data([index]); - - //When dragging the index line, turn off line transitions. - // Then turn them back on when done dragging. - var oldDuration = chart.duration(); - chart.duration(0); - chart.update(); - chart.duration(oldDuration); - } - - g.select('.nv-background rect') - .on('click', function() { - index.x = d3.mouse(this)[0]; - index.i = Math.round(dx.invert(index.x)); - - // update state and send stateChange with new index - state.index = index.i; - dispatch.stateChange(state); - - updateZero(); - }); - - lines.dispatch.on('elementClick', function(e) { - index.i = e.pointIndex; - index.x = dx(index.i); - - // update state and send stateChange with new index - state.index = index.i; - dispatch.stateChange(state); - - updateZero(); - }); - - controls.dispatch.on('legendClick', function(d,i) { - d.disabled = !d.disabled; - rescaleY = !d.disabled; - - state.rescaleY = rescaleY; - dispatch.stateChange(state); - chart.update(); - }); - - legend.dispatch.on('stateChange', function(newState) { - for (var key in newState) - state[key] = newState[key]; - dispatch.stateChange(state); - chart.update(); - }); - - interactiveLayer.dispatch.on('elementMousemove', function(e) { - lines.clearHighlights(); - var singlePoint, pointIndex, pointXLocation, allData = []; - - data - .filter(function(series, i) { - series.seriesIndex = i; - return !series.disabled; - }) - .forEach(function(series,i) { - pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x()); - lines.highlightPoint(i, pointIndex, true); - var point = series.values[pointIndex]; - if (typeof point === 'undefined') return; - if (typeof singlePoint === 'undefined') singlePoint = point; - if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex)); - allData.push({ - key: series.key, - value: chart.y()(point, pointIndex), - color: color(series,series.seriesIndex) - }); - }); - - //Highlight the tooltip entry based on which point the mouse is closest to. - if (allData.length > 2) { - var yValue = chart.yScale().invert(e.mouseY); - var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]); - var threshold = 0.03 * domainExtent; - var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold); - if (indexToHighlight !== null) - allData[indexToHighlight].highlight = true; - } - - var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex), pointIndex); - interactiveLayer.tooltip - .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top}) - .chartContainer(that.parentNode) - .valueFormatter(function(d,i) { - return yAxis.tickFormat()(d); - }) - .data( - { - value: xValue, - series: allData - } - )(); - - interactiveLayer.renderGuideLine(pointXLocation); - }); - - interactiveLayer.dispatch.on("elementMouseout",function(e) { - lines.clearHighlights(); - }); - - // Update chart from a state object passed to event handler - dispatch.on('changeState', function(e) { - if (typeof e.disabled !== 'undefined') { - data.forEach(function(series,i) { - series.disabled = e.disabled[i]; - }); - - state.disabled = e.disabled; - } - - if (typeof e.index !== 'undefined') { - index.i = e.index; - index.x = dx(index.i); - - state.index = e.index; - - indexLine - .data([index]); - } - - if (typeof e.rescaleY !== 'undefined') { - rescaleY = e.rescaleY; - } - - chart.update(); - }); - - }); - - renderWatch.renderEnd('cumulativeLineChart immediate'); - - return chart; - } - - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ - - lines.dispatch.on('elementMouseover.tooltip', function(evt) { - var point = { - x: chart.x()(evt.point), - y: chart.y()(evt.point), - color: evt.point.color - }; - evt.point = point; - tooltip.data(evt).position(evt.pos).hidden(false); - }); - - lines.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true) - }); - - //============================================================ - // Functions - //------------------------------------------------------------ - - var indexifyYGetter = null; - /* Normalize the data according to an index point. */ - function indexify(idx, data) { - if (!indexifyYGetter) indexifyYGetter = lines.y(); - return data.map(function(line, i) { - if (!line.values) { - return line; - } - var indexValue = line.values[idx]; - if (indexValue == null) { - return line; - } - var v = indexifyYGetter(indexValue, idx); - - //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue - if (v < -.95 && !noErrorCheck) { - //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100) - - line.tempDisabled = true; - return line; - } - - line.tempDisabled = false; - - line.values = line.values.map(function(point, pointIndex) { - point.display = {'y': (indexifyYGetter(point, pointIndex) - v) / (1 + v) }; - return point; - }); - - return line; - }) - } - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - // expose chart's sub-components - chart.dispatch = dispatch; - chart.lines = lines; - chart.legend = legend; - chart.controls = controls; - chart.xAxis = xAxis; - chart.yAxis = yAxis; - chart.interactiveLayer = interactiveLayer; - chart.state = state; - chart.tooltip = tooltip; - - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - rescaleY: {get: function(){return rescaleY;}, set: function(_){rescaleY=_;}}, - showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}}, - showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, - average: {get: function(){return average;}, set: function(_){average=_;}}, - defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, - noData: {get: function(){return noData;}, set: function(_){noData=_;}}, - showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, - showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, - noErrorCheck: {get: function(){return noErrorCheck;}, set: function(_){noErrorCheck=_;}}, - - // deprecated options - tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); - tooltip.enabled(!!_); - }}, - tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); - tooltip.contentGenerator(_); - }}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - legend.color(color); - }}, - useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){ - useInteractiveGuideline = _; - if (_ === true) { - chart.interactive(false); - chart.useVoronoi(false); - } - }}, - rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ - rightAlignYAxis = _; - yAxis.orient( (_) ? 'right' : 'left'); - }}, - duration: {get: function(){return duration;}, set: function(_){ - duration = _; - lines.duration(duration); - xAxis.duration(duration); - yAxis.duration(duration); - renderWatch.reset(duration); - }} - }); - - nv.utils.inheritOptions(chart, lines); - nv.utils.initOptions(chart); - - return chart; - }; -//TODO: consider deprecating by adding necessary features to multiBar model - nv.models.discreteBar = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = 960 - , height = 500 - , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one - , container - , x = d3.scale.ordinal() - , y = d3.scale.linear() - , getX = function(d) { return d.x } - , getY = function(d) { return d.y } - , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove - , color = nv.utils.defaultColor() - , showValues = false - , valueFormat = d3.format(',.2f') - , xDomain - , yDomain - , xRange - , yRange - , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') - , rectClass = 'discreteBar' - , duration = 250 - ; - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - var x0, y0; - var renderWatch = nv.utils.renderWatch(dispatch, duration); - - function chart(selection) { - renderWatch.reset(); - selection.each(function(data) { - var availableWidth = width - margin.left - margin.right, - availableHeight = height - margin.top - margin.bottom; - - container = d3.select(this); - nv.utils.initSVG(container); - - //add series index to each data point for reference - data.forEach(function(series, i) { - series.values.forEach(function(point) { - point.series = i; - }); - }); - - // Setup Scales - // remap and flatten the data for use in calculating the scales' domains - var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate - data.map(function(d) { - return d.values.map(function(d,i) { - return { x: getX(d,i), y: getY(d,i), y0: d.y0 } - }) - }); - - x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x })) - .rangeBands(xRange || [0, availableWidth], .1); - y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY))); - - // If showValues, pad the Y axis range to account for label height - if (showValues) y.range(yRange || [availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]); - else y.range(yRange || [availableHeight, 0]); - - //store old scales if they exist - x0 = x0 || x; - y0 = y0 || y.copy().range([y(0),y(0)]); - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); - - gEnter.append('g').attr('class', 'nv-groups'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - //TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later - var groups = wrap.select('.nv-groups').selectAll('.nv-group') - .data(function(d) { return d }, function(d) { return d.key }); - groups.enter().append('g') - .style('stroke-opacity', 1e-6) - .style('fill-opacity', 1e-6); - groups.exit() - .watchTransition(renderWatch, 'discreteBar: exit groups') - .style('stroke-opacity', 1e-6) - .style('fill-opacity', 1e-6) - .remove(); - groups - .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) - .classed('hover', function(d) { return d.hover }); - groups - .watchTransition(renderWatch, 'discreteBar: groups') - .style('stroke-opacity', 1) - .style('fill-opacity', .75); - - var bars = groups.selectAll('g.nv-bar') - .data(function(d) { return d.values }); - bars.exit().remove(); - - var barsEnter = bars.enter().append('g') - .attr('transform', function(d,i,j) { - return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')' - }) - .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here - d3.select(this).classed('hover', true); - dispatch.elementMouseover({ - data: d, - index: i, - color: d3.select(this).style("fill") - }); - }) - .on('mouseout', function(d,i) { - d3.select(this).classed('hover', false); - dispatch.elementMouseout({ - data: d, - index: i, - color: d3.select(this).style("fill") - }); - }) - .on('mousemove', function(d,i) { - dispatch.elementMousemove({ - data: d, - index: i, - color: d3.select(this).style("fill") - }); - }) - .on('click', function(d,i) { - dispatch.elementClick({ - data: d, - index: i, - color: d3.select(this).style("fill") - }); - d3.event.stopPropagation(); - }) - .on('dblclick', function(d,i) { - dispatch.elementDblClick({ - data: d, - index: i, - color: d3.select(this).style("fill") - }); - d3.event.stopPropagation(); - }); - - barsEnter.append('rect') - .attr('height', 0) - .attr('width', x.rangeBand() * .9 / data.length ) - - if (showValues) { - barsEnter.append('text') - .attr('text-anchor', 'middle') - ; - - bars.select('text') - .text(function(d,i) { return valueFormat(getY(d,i)) }) - .watchTransition(renderWatch, 'discreteBar: bars text') - .attr('x', x.rangeBand() * .9 / 2) - .attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 }) - - ; - } else { - bars.selectAll('text').remove(); - } - - bars - .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' }) - .style('fill', function(d,i) { return d.color || color(d,i) }) - .style('stroke', function(d,i) { return d.color || color(d,i) }) - .select('rect') - .attr('class', rectClass) - .watchTransition(renderWatch, 'discreteBar: bars rect') - .attr('width', x.rangeBand() * .9 / data.length); - bars.watchTransition(renderWatch, 'discreteBar: bars') - //.delay(function(d,i) { return i * 1200 / data[0].values.length }) - .attr('transform', function(d,i) { - var left = x(getX(d,i)) + x.rangeBand() * .05, - top = getY(d,i) < 0 ? - y(0) : - y(0) - y(getY(d,i)) < 1 ? - y(0) - 1 : //make 1 px positive bars show up above y=0 - y(getY(d,i)); - - return 'translate(' + left + ', ' + top + ')' - }) - .select('rect') - .attr('height', function(d,i) { - return Math.max(Math.abs(y(getY(d,i)) - y((yDomain && yDomain[0]) || 0)) || 1) - }); - - - //store old scales for use in transitions on update - x0 = x.copy(); - y0 = y.copy(); - - }); - - renderWatch.renderEnd('discreteBar immediate'); - return chart; - } - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, - showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}}, - x: {get: function(){return getX;}, set: function(_){getX=_;}}, - y: {get: function(){return getY;}, set: function(_){getY=_;}}, - xScale: {get: function(){return x;}, set: function(_){x=_;}}, - yScale: {get: function(){return y;}, set: function(_){y=_;}}, - xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, - yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, - xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, - yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, - valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}}, - id: {get: function(){return id;}, set: function(_){id=_;}}, - rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - }}, - duration: {get: function(){return duration;}, set: function(_){ - duration = _; - renderWatch.reset(duration); - }} - }); - - nv.utils.initOptions(chart); - - return chart; - }; - - nv.models.discreteBarChart = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var discretebar = nv.models.discreteBar() - , xAxis = nv.models.axis() - , yAxis = nv.models.axis() - , tooltip = nv.models.tooltip() - ; - - var margin = {top: 15, right: 10, bottom: 50, left: 60} - , width = null - , height = null - , color = nv.utils.getColor() - , showXAxis = true - , showYAxis = true - , rightAlignYAxis = false - , staggerLabels = false - , x - , y - , noData = null - , dispatch = d3.dispatch('beforeUpdate','renderEnd') - , duration = 250 - ; - - xAxis - .orient('bottom') - .showMaxMin(false) - .tickFormat(function(d) { return d }) - ; - yAxis - .orient((rightAlignYAxis) ? 'right' : 'left') - .tickFormat(d3.format(',.1f')) - ; - - tooltip - .duration(0) - .headerEnabled(false) - .valueFormatter(function(d, i) { - return yAxis.tickFormat()(d, i); - }) - .keyFormatter(function(d, i) { - return xAxis.tickFormat()(d, i); - }); - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - var renderWatch = nv.utils.renderWatch(dispatch, duration); - - function chart(selection) { - renderWatch.reset(); - renderWatch.models(discretebar); - if (showXAxis) renderWatch.models(xAxis); - if (showYAxis) renderWatch.models(yAxis); - - selection.each(function(data) { - var container = d3.select(this), - that = this; - nv.utils.initSVG(container); - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); - - chart.update = function() { - dispatch.beforeUpdate(); - container.transition().duration(duration).call(chart); - }; - chart.container = this; - - // Display No Data message if there's nothing to show. - if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { - nv.utils.noData(chart, container); - return chart; - } else { - container.selectAll('.nv-noData').remove(); - } - - // Setup Scales - x = discretebar.xScale(); - y = discretebar.yScale().clamp(true); - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g'); - var defsEnter = gEnter.append('defs'); - var g = wrap.select('g'); - - gEnter.append('g').attr('class', 'nv-x nv-axis'); - gEnter.append('g').attr('class', 'nv-y nv-axis') - .append('g').attr('class', 'nv-zeroLine') - .append('line'); - - gEnter.append('g').attr('class', 'nv-barsWrap'); - - g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - if (rightAlignYAxis) { - g.select(".nv-y.nv-axis") - .attr("transform", "translate(" + availableWidth + ",0)"); - } - - // Main Chart Component(s) - discretebar - .width(availableWidth) - .height(availableHeight); - - var barsWrap = g.select('.nv-barsWrap') - .datum(data.filter(function(d) { return !d.disabled })); - - barsWrap.transition().call(discretebar); - - - defsEnter.append('clipPath') - .attr('id', 'nv-x-label-clip-' + discretebar.id()) - .append('rect'); - - g.select('#nv-x-label-clip-' + discretebar.id() + ' rect') - .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1)) - .attr('height', 16) - .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 )); - - // Setup Axes - if (showXAxis) { - xAxis - .scale(x) - ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) - .tickSize(-availableHeight, 0); - - g.select('.nv-x.nv-axis') - .attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')'); - g.select('.nv-x.nv-axis').call(xAxis); - - var xTicks = g.select('.nv-x.nv-axis').selectAll('g'); - if (staggerLabels) { - xTicks - .selectAll('text') - .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' }) - } - } - - if (showYAxis) { - yAxis - .scale(y) - ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) - .tickSize( -availableWidth, 0); - - g.select('.nv-y.nv-axis').call(yAxis); - } - - // Zero line - g.select(".nv-zeroLine line") - .attr("x1",0) - .attr("x2",availableWidth) - .attr("y1", y(0)) - .attr("y2", y(0)) - ; - }); - - renderWatch.renderEnd('discreteBar chart immediate'); - return chart; - } - - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ - - discretebar.dispatch.on('elementMouseover.tooltip', function(evt) { - evt['series'] = { - key: chart.x()(evt.data), - value: chart.y()(evt.data), - color: evt.color - }; - tooltip.data(evt).hidden(false); - }); - - discretebar.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true); - }); - - discretebar.dispatch.on('elementMousemove.tooltip', function(evt) { - tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); - }); - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - chart.dispatch = dispatch; - chart.discretebar = discretebar; - chart.xAxis = xAxis; - chart.yAxis = yAxis; - chart.tooltip = tooltip; - - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}}, - showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, - showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, - noData: {get: function(){return noData;}, set: function(_){noData=_;}}, - - // deprecated options - tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); - tooltip.enabled(!!_); - }}, - tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); - tooltip.contentGenerator(_); - }}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - duration: {get: function(){return duration;}, set: function(_){ - duration = _; - renderWatch.reset(duration); - discretebar.duration(duration); - xAxis.duration(duration); - yAxis.duration(duration); - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - discretebar.color(color); - }}, - rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ - rightAlignYAxis = _; - yAxis.orient( (_) ? 'right' : 'left'); - }} - }); - - nv.utils.inheritOptions(chart, discretebar); - nv.utils.initOptions(chart); - - return chart; - } - - nv.models.distribution = function() { - "use strict"; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = 400 //technically width or height depending on x or y.... - , size = 8 - , axis = 'x' // 'x' or 'y'... horizontal or vertical - , getData = function(d) { return d[axis] } // defaults d.x or d.y - , color = nv.utils.defaultColor() - , scale = d3.scale.linear() - , domain - , duration = 250 - , dispatch = d3.dispatch('renderEnd') - ; - - //============================================================ - - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - var scale0; - var renderWatch = nv.utils.renderWatch(dispatch, duration); - - //============================================================ - - - function chart(selection) { - renderWatch.reset(); - selection.each(function(data) { - var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom), - naxis = axis == 'x' ? 'y' : 'x', - container = d3.select(this); - nv.utils.initSVG(container); - - //------------------------------------------------------------ - // Setup Scales - - scale0 = scale0 || scale; - - //------------------------------------------------------------ - - - //------------------------------------------------------------ - // Setup containers and skeleton of chart - - var wrap = container.selectAll('g.nv-distribution').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') - - //------------------------------------------------------------ - - - var distWrap = g.selectAll('g.nv-dist') - .data(function(d) { return d }, function(d) { return d.key }); - - distWrap.enter().append('g'); - distWrap - .attr('class', function(d,i) { return 'nv-dist nv-series-' + i }) - .style('stroke', function(d,i) { return color(d, i) }); - - var dist = distWrap.selectAll('line.nv-dist' + axis) - .data(function(d) { return d.values }) - dist.enter().append('line') - .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) }) - .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) }) - renderWatch.transition(distWrap.exit().selectAll('line.nv-dist' + axis), 'dist exit') - // .transition() - .attr(axis + '1', function(d,i) { return scale(getData(d,i)) }) - .attr(axis + '2', function(d,i) { return scale(getData(d,i)) }) - .style('stroke-opacity', 0) - .remove(); - dist - .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i }) - .attr(naxis + '1', 0) - .attr(naxis + '2', size); - renderWatch.transition(dist, 'dist') - // .transition() - .attr(axis + '1', function(d,i) { return scale(getData(d,i)) }) - .attr(axis + '2', function(d,i) { return scale(getData(d,i)) }) - - - scale0 = scale.copy(); - - }); - renderWatch.renderEnd('distribution immediate'); - return chart; - } - - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - chart.options = nv.utils.optionsFunc.bind(chart); - chart.dispatch = dispatch; - - chart.margin = function(_) { - if (!arguments.length) return margin; - margin.top = typeof _.top != 'undefined' ? _.top : margin.top; - margin.right = typeof _.right != 'undefined' ? _.right : margin.right; - margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; - margin.left = typeof _.left != 'undefined' ? _.left : margin.left; - return chart; - }; - - chart.width = function(_) { - if (!arguments.length) return width; - width = _; - return chart; - }; - - chart.axis = function(_) { - if (!arguments.length) return axis; - axis = _; - return chart; - }; - - chart.size = function(_) { - if (!arguments.length) return size; - size = _; - return chart; - }; - - chart.getData = function(_) { - if (!arguments.length) return getData; - getData = d3.functor(_); - return chart; - }; - - chart.scale = function(_) { - if (!arguments.length) return scale; - scale = _; - return chart; - }; - - chart.color = function(_) { - if (!arguments.length) return color; - color = nv.utils.getColor(_); - return chart; - }; - - chart.duration = function(_) { - if (!arguments.length) return duration; - duration = _; - renderWatch.reset(duration); - return chart; - }; - //============================================================ - - - return chart; - } - nv.models.furiousLegend = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var margin = {top: 5, right: 0, bottom: 5, left: 0} - , width = 400 - , height = 20 - , getKey = function(d) { return d.key } - , color = nv.utils.getColor() - , align = true - , padding = 28 //define how much space between legend items. - recommend 32 for furious version - , rightAlign = true - , updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch. - , radioButtonMode = false //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time) - , expanded = false - , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange') - , vers = 'classic' //Options are "classic" and "furious" - ; - - function chart(selection) { - selection.each(function(data) { - var availableWidth = width - margin.left - margin.right, - container = d3.select(this); - nv.utils.initSVG(container); - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-legend').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g'); - var g = wrap.select('g'); - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - var series = g.selectAll('.nv-series') - .data(function(d) { - if(vers != 'furious') return d; - - return d.filter(function(n) { - return expanded ? true : !n.disengaged; - }); - }); - var seriesEnter = series.enter().append('g').attr('class', 'nv-series') - - var seriesShape; - - if(vers == 'classic') { - seriesEnter.append('circle') - .style('stroke-width', 2) - .attr('class','nv-legend-symbol') - .attr('r', 5); - - seriesShape = series.select('circle'); - } else if (vers == 'furious') { - seriesEnter.append('rect') - .style('stroke-width', 2) - .attr('class','nv-legend-symbol') - .attr('rx', 3) - .attr('ry', 3); - - seriesShape = series.select('rect'); - - seriesEnter.append('g') - .attr('class', 'nv-check-box') - .property('innerHTML','') - .attr('transform', 'translate(-10,-8)scale(0.5)'); - - var seriesCheckbox = series.select('.nv-check-box'); - - seriesCheckbox.each(function(d,i) { - d3.select(this).selectAll('path') - .attr('stroke', setTextColor(d,i)); - }); - } - - seriesEnter.append('text') - .attr('text-anchor', 'start') - .attr('class','nv-legend-text') - .attr('dy', '.32em') - .attr('dx', '8'); - - var seriesText = series.select('text.nv-legend-text'); - - series - .on('mouseover', function(d,i) { - dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects - }) - .on('mouseout', function(d,i) { - dispatch.legendMouseout(d,i); - }) - .on('click', function(d,i) { - dispatch.legendClick(d,i); - // make sure we re-get data in case it was modified - var data = series.data(); - if (updateState) { - if(vers =='classic') { - if (radioButtonMode) { - //Radio button mode: set every series to disabled, - // and enable the clicked series. - data.forEach(function(series) { series.disabled = true}); - d.disabled = false; - } - else { - d.disabled = !d.disabled; - if (data.every(function(series) { return series.disabled})) { - //the default behavior of NVD3 legends is, if every single series - // is disabled, turn all series' back on. - data.forEach(function(series) { series.disabled = false}); - } - } - } else if(vers == 'furious') { - if(expanded) { - d.disengaged = !d.disengaged; - d.userDisabled = d.userDisabled == undefined ? !!d.disabled : d.userDisabled; - d.disabled = d.disengaged || d.userDisabled; - } else if (!expanded) { - d.disabled = !d.disabled; - d.userDisabled = d.disabled; - var engaged = data.filter(function(d) { return !d.disengaged; }); - if (engaged.every(function(series) { return series.userDisabled })) { - //the default behavior of NVD3 legends is, if every single series - // is disabled, turn all series' back on. - data.forEach(function(series) { - series.disabled = series.userDisabled = false; - }); - } - } - } - dispatch.stateChange({ - disabled: data.map(function(d) { return !!d.disabled }), - disengaged: data.map(function(d) { return !!d.disengaged }) - }); - - } - }) - .on('dblclick', function(d,i) { - if(vers == 'furious' && expanded) return; - dispatch.legendDblclick(d,i); - if (updateState) { - // make sure we re-get data in case it was modified - var data = series.data(); - //the default behavior of NVD3 legends, when double clicking one, - // is to set all other series' to false, and make the double clicked series enabled. - data.forEach(function(series) { - series.disabled = true; - if(vers == 'furious') series.userDisabled = series.disabled; - }); - d.disabled = false; - if(vers == 'furious') d.userDisabled = d.disabled; - dispatch.stateChange({ - disabled: data.map(function(d) { return !!d.disabled }) - }); - } - }); - - series.classed('nv-disabled', function(d) { return d.userDisabled }); - series.exit().remove(); - - seriesText - .attr('fill', setTextColor) - .text(getKey); - - //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option) - // NEW ALIGNING CODE, TODO: clean up - - var versPadding; - switch(vers) { - case 'furious' : - versPadding = 23; - break; - case 'classic' : - versPadding = 20; - } - - if (align) { - - var seriesWidths = []; - series.each(function(d,i) { - var legendText = d3.select(this).select('text'); - var nodeTextLength; - try { - nodeTextLength = legendText.node().getComputedTextLength(); - // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead - if(nodeTextLength <= 0) throw Error(); - } - catch(e) { - nodeTextLength = nv.utils.calcApproxTextWidth(legendText); - } - - seriesWidths.push(nodeTextLength + padding); - }); - - var seriesPerRow = 0; - var legendWidth = 0; - var columnWidths = []; - - while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) { - columnWidths[seriesPerRow] = seriesWidths[seriesPerRow]; - legendWidth += seriesWidths[seriesPerRow++]; - } - if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row - - while ( legendWidth > availableWidth && seriesPerRow > 1 ) { - columnWidths = []; - seriesPerRow--; - - for (var k = 0; k < seriesWidths.length; k++) { - if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) ) - columnWidths[k % seriesPerRow] = seriesWidths[k]; - } - - legendWidth = columnWidths.reduce(function(prev, cur, index, array) { - return prev + cur; - }); - } - - var xPositions = []; - for (var i = 0, curX = 0; i < seriesPerRow; i++) { - xPositions[i] = curX; - curX += columnWidths[i]; - } - - series - .attr('transform', function(d, i) { - return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')'; - }); - - //position legend as far right as possible within the total width - if (rightAlign) { - g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')'); - } - else { - g.attr('transform', 'translate(0' + ',' + margin.top + ')'); - } - - height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding); - - } else { - - var ypos = 5, - newxpos = 5, - maxwidth = 0, - xpos; - series - .attr('transform', function(d, i) { - var length = d3.select(this).select('text').node().getComputedTextLength() + padding; - xpos = newxpos; - - if (width < margin.left + margin.right + xpos + length) { - newxpos = xpos = 5; - ypos += versPadding; - } - - newxpos += length; - if (newxpos > maxwidth) maxwidth = newxpos; - - return 'translate(' + xpos + ',' + ypos + ')'; - }); - - //position legend as far right as possible within the total width - g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')'); - - height = margin.top + margin.bottom + ypos + 15; - } - - if(vers == 'furious') { - // Size rectangles after text is placed - seriesShape - .attr('width', function(d,i) { - return seriesText[0][i].getComputedTextLength() + 27; - }) - .attr('height', 18) - .attr('y', -9) - .attr('x', -15) - } - - seriesShape - .style('fill', setBGColor) - .style('stroke', function(d,i) { return d.color || color(d, i) }); - }); - - function setTextColor(d,i) { - if(vers != 'furious') return '#000'; - if(expanded) { - return d.disengaged ? color(d,i) : '#fff'; - } else if (!expanded) { - return !!d.disabled ? color(d,i) : '#fff'; - } - } - - function setBGColor(d,i) { - if(expanded && vers == 'furious') { - return d.disengaged ? '#fff' : color(d,i); - } else { - return !!d.disabled ? '#fff' : color(d,i); - } - } - - return chart; - } - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - key: {get: function(){return getKey;}, set: function(_){getKey=_;}}, - align: {get: function(){return align;}, set: function(_){align=_;}}, - rightAlign: {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}}, - padding: {get: function(){return padding;}, set: function(_){padding=_;}}, - updateState: {get: function(){return updateState;}, set: function(_){updateState=_;}}, - radioButtonMode: {get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}}, - expanded: {get: function(){return expanded;}, set: function(_){expanded=_;}}, - vers: {get: function(){return vers;}, set: function(_){vers=_;}}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - }} - }); - - nv.utils.initOptions(chart); - - return chart; - }; -//TODO: consider deprecating and using multibar with single series for this - nv.models.historicalBar = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = null - , height = null - , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one - , container = null - , x = d3.scale.linear() - , y = d3.scale.linear() - , getX = function(d) { return d.x } - , getY = function(d) { return d.y } - , forceX = [] - , forceY = [0] - , padData = false - , clipEdge = true - , color = nv.utils.defaultColor() - , xDomain - , yDomain - , xRange - , yRange - , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') - , interactive = true - ; - - var renderWatch = nv.utils.renderWatch(dispatch, 0); - - function chart(selection) { - selection.each(function(data) { - renderWatch.reset(); - - container = d3.select(this); - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); - - nv.utils.initSVG(container); - - // Setup Scales - x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) )); - - if (padData) - x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); - else - x.range(xRange || [0, availableWidth]); - - y.domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) )) - .range(yRange || [availableHeight, 0]); - - // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point - if (x.domain()[0] === x.domain()[1]) - x.domain()[0] ? - x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) - : x.domain([-1,1]); - - if (y.domain()[0] === y.domain()[1]) - y.domain()[0] ? - y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) - : y.domain([-1,1]); - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-historicalBar-' + id).data([data[0].values]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBar-' + id); - var defsEnter = wrapEnter.append('defs'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); - - gEnter.append('g').attr('class', 'nv-bars'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - container - .on('click', function(d,i) { - dispatch.chartClick({ - data: d, - index: i, - pos: d3.event, - id: id - }); - }); - - defsEnter.append('clipPath') - .attr('id', 'nv-chart-clip-path-' + id) - .append('rect'); - - wrap.select('#nv-chart-clip-path-' + id + ' rect') - .attr('width', availableWidth) - .attr('height', availableHeight); - - g.attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : ''); - - var bars = wrap.select('.nv-bars').selectAll('.nv-bar') - .data(function(d) { return d }, function(d,i) {return getX(d,i)}); - bars.exit().remove(); - - bars.enter().append('rect') - .attr('x', 0 ) - .attr('y', function(d,i) { return nv.utils.NaNtoZero(y(Math.max(0, getY(d,i)))) }) - .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.abs(y(getY(d,i)) - y(0))) }) - .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; }) - .on('mouseover', function(d,i) { - if (!interactive) return; - d3.select(this).classed('hover', true); - dispatch.elementMouseover({ - data: d, - index: i, - color: d3.select(this).style("fill") - }); - - }) - .on('mouseout', function(d,i) { - if (!interactive) return; - d3.select(this).classed('hover', false); - dispatch.elementMouseout({ - data: d, - index: i, - color: d3.select(this).style("fill") - }); - }) - .on('mousemove', function(d,i) { - if (!interactive) return; - dispatch.elementMousemove({ - data: d, - index: i, - color: d3.select(this).style("fill") - }); - }) - .on('click', function(d,i) { - if (!interactive) return; - dispatch.elementClick({ - data: d, - index: i, - color: d3.select(this).style("fill") - }); - d3.event.stopPropagation(); - }) - .on('dblclick', function(d,i) { - if (!interactive) return; - dispatch.elementDblClick({ - data: d, - index: i, - color: d3.select(this).style("fill") - }); - d3.event.stopPropagation(); - }); - - bars - .attr('fill', function(d,i) { return color(d, i); }) - .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i }) - .watchTransition(renderWatch, 'bars') - .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; }) - //TODO: better width calculations that don't assume always uniform data spacing;w - .attr('width', (availableWidth / data[0].values.length) * .9 ); - - bars.watchTransition(renderWatch, 'bars') - .attr('y', function(d,i) { - var rval = getY(d,i) < 0 ? - y(0) : - y(0) - y(getY(d,i)) < 1 ? - y(0) - 1 : - y(getY(d,i)); - return nv.utils.NaNtoZero(rval); - }) - .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.max(Math.abs(y(getY(d,i)) - y(0)),1)) }); - - }); - - renderWatch.renderEnd('historicalBar immediate'); - return chart; - } - - //Create methods to allow outside functions to highlight a specific bar. - chart.highlightPoint = function(pointIndex, isHoverOver) { - container - .select(".nv-bars .nv-bar-0-" + pointIndex) - .classed("hover", isHoverOver) - ; - }; - - chart.clearHighlights = function() { - container - .select(".nv-bars .nv-bar.hover") - .classed("hover", false) - ; - }; - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}}, - forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, - padData: {get: function(){return padData;}, set: function(_){padData=_;}}, - x: {get: function(){return getX;}, set: function(_){getX=_;}}, - y: {get: function(){return getY;}, set: function(_){getY=_;}}, - xScale: {get: function(){return x;}, set: function(_){x=_;}}, - yScale: {get: function(){return y;}, set: function(_){y=_;}}, - xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, - yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, - xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, - yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, - clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, - id: {get: function(){return id;}, set: function(_){id=_;}}, - interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - }} - }); - - nv.utils.initOptions(chart); - - return chart; - }; - - nv.models.historicalBarChart = function(bar_model) { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var bars = bar_model || nv.models.historicalBar() - , xAxis = nv.models.axis() - , yAxis = nv.models.axis() - , legend = nv.models.legend() - , interactiveLayer = nv.interactiveGuideline() - , tooltip = nv.models.tooltip() - ; - - - var margin = {top: 30, right: 90, bottom: 50, left: 90} - , color = nv.utils.defaultColor() - , width = null - , height = null - , showLegend = false - , showXAxis = true - , showYAxis = true - , rightAlignYAxis = false - , useInteractiveGuideline = false - , x - , y - , state = {} - , defaultState = null - , noData = null - , dispatch = d3.dispatch('tooltipHide', 'stateChange', 'changeState', 'renderEnd') - , transitionDuration = 250 - ; - - xAxis.orient('bottom').tickPadding(7); - yAxis.orient( (rightAlignYAxis) ? 'right' : 'left'); - tooltip - .duration(0) - .headerEnabled(false) - .valueFormatter(function(d, i) { - return yAxis.tickFormat()(d, i); - }) - .headerFormatter(function(d, i) { - return xAxis.tickFormat()(d, i); - }); - - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - var renderWatch = nv.utils.renderWatch(dispatch, 0); - - function chart(selection) { - selection.each(function(data) { - renderWatch.reset(); - renderWatch.models(bars); - if (showXAxis) renderWatch.models(xAxis); - if (showYAxis) renderWatch.models(yAxis); - - var container = d3.select(this), - that = this; - nv.utils.initSVG(container); - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); - - chart.update = function() { container.transition().duration(transitionDuration).call(chart) }; - chart.container = this; - - //set state.disabled - state.disabled = data.map(function(d) { return !!d.disabled }); - - if (!defaultState) { - var key; - defaultState = {}; - for (key in state) { - if (state[key] instanceof Array) - defaultState[key] = state[key].slice(0); - else - defaultState[key] = state[key]; - } - } - - // Display noData message if there's nothing to show. - if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { - nv.utils.noData(chart, container) - return chart; - } else { - container.selectAll('.nv-noData').remove(); - } - - // Setup Scales - x = bars.xScale(); - y = bars.yScale(); - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-historicalBarChart').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBarChart').append('g'); - var g = wrap.select('g'); - - gEnter.append('g').attr('class', 'nv-x nv-axis'); - gEnter.append('g').attr('class', 'nv-y nv-axis'); - gEnter.append('g').attr('class', 'nv-barsWrap'); - gEnter.append('g').attr('class', 'nv-legendWrap'); - gEnter.append('g').attr('class', 'nv-interactive'); - - // Legend - if (showLegend) { - legend.width(availableWidth); - - g.select('.nv-legendWrap') - .datum(data) - .call(legend); - - if ( margin.top != legend.height()) { - margin.top = legend.height(); - availableHeight = nv.utils.availableHeight(height, container, margin); - } - - wrap.select('.nv-legendWrap') - .attr('transform', 'translate(0,' + (-margin.top) +')') - } - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - if (rightAlignYAxis) { - g.select(".nv-y.nv-axis") - .attr("transform", "translate(" + availableWidth + ",0)"); - } - - //Set up interactive layer - if (useInteractiveGuideline) { - interactiveLayer - .width(availableWidth) - .height(availableHeight) - .margin({left:margin.left, top:margin.top}) - .svgContainer(container) - .xScale(x); - wrap.select(".nv-interactive").call(interactiveLayer); - } - bars - .width(availableWidth) - .height(availableHeight) - .color(data.map(function(d,i) { - return d.color || color(d, i); - }).filter(function(d,i) { return !data[i].disabled })); - - var barsWrap = g.select('.nv-barsWrap') - .datum(data.filter(function(d) { return !d.disabled })); - barsWrap.transition().call(bars); - - // Setup Axes - if (showXAxis) { - xAxis - .scale(x) - ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) - .tickSize(-availableHeight, 0); - - g.select('.nv-x.nv-axis') - .attr('transform', 'translate(0,' + y.range()[0] + ')'); - g.select('.nv-x.nv-axis') - .transition() - .call(xAxis); - } - - if (showYAxis) { - yAxis - .scale(y) - ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) - .tickSize( -availableWidth, 0); - - g.select('.nv-y.nv-axis') - .transition() - .call(yAxis); - } - - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ - - interactiveLayer.dispatch.on('elementMousemove', function(e) { - bars.clearHighlights(); - - var singlePoint, pointIndex, pointXLocation, allData = []; - data - .filter(function(series, i) { - series.seriesIndex = i; - return !series.disabled; - }) - .forEach(function(series,i) { - pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x()); - bars.highlightPoint(pointIndex,true); - var point = series.values[pointIndex]; - if (point === undefined) return; - if (singlePoint === undefined) singlePoint = point; - if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex)); - allData.push({ - key: series.key, - value: chart.y()(point, pointIndex), - color: color(series,series.seriesIndex), - data: series.values[pointIndex] - }); - }); - - var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex)); - interactiveLayer.tooltip - .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top}) - .chartContainer(that.parentNode) - .valueFormatter(function(d,i) { - return yAxis.tickFormat()(d); - }) - .data({ - value: xValue, - index: pointIndex, - series: allData - })(); - - interactiveLayer.renderGuideLine(pointXLocation); - - }); - - interactiveLayer.dispatch.on("elementMouseout",function(e) { - dispatch.tooltipHide(); - bars.clearHighlights(); - }); - - legend.dispatch.on('legendClick', function(d,i) { - d.disabled = !d.disabled; - - if (!data.filter(function(d) { return !d.disabled }).length) { - data.map(function(d) { - d.disabled = false; - wrap.selectAll('.nv-series').classed('disabled', false); - return d; - }); - } - - state.disabled = data.map(function(d) { return !!d.disabled }); - dispatch.stateChange(state); - - selection.transition().call(chart); - }); - - legend.dispatch.on('legendDblclick', function(d) { - //Double clicking should always enable current series, and disabled all others. - data.forEach(function(d) { - d.disabled = true; - }); - d.disabled = false; - - state.disabled = data.map(function(d) { return !!d.disabled }); - dispatch.stateChange(state); - chart.update(); - }); - - dispatch.on('changeState', function(e) { - if (typeof e.disabled !== 'undefined') { - data.forEach(function(series,i) { - series.disabled = e.disabled[i]; - }); - - state.disabled = e.disabled; - } - - chart.update(); - }); - }); - - renderWatch.renderEnd('historicalBarChart immediate'); - return chart; - } - - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ - - bars.dispatch.on('elementMouseover.tooltip', function(evt) { - evt['series'] = { - key: chart.x()(evt.data), - value: chart.y()(evt.data), - color: evt.color - }; - tooltip.data(evt).hidden(false); - }); - - bars.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true); - }); - - bars.dispatch.on('elementMousemove.tooltip', function(evt) { - tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); - }); - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - // expose chart's sub-components - chart.dispatch = dispatch; - chart.bars = bars; - chart.legend = legend; - chart.xAxis = xAxis; - chart.yAxis = yAxis; - chart.interactiveLayer = interactiveLayer; - chart.tooltip = tooltip; - - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, - showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, - showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, - defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, - noData: {get: function(){return noData;}, set: function(_){noData=_;}}, - - // deprecated options - tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); - tooltip.enabled(!!_); - }}, - tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); - tooltip.contentGenerator(_); - }}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - legend.color(color); - bars.color(color); - }}, - duration: {get: function(){return transitionDuration;}, set: function(_){ - transitionDuration=_; - renderWatch.reset(transitionDuration); - yAxis.duration(transitionDuration); - xAxis.duration(transitionDuration); - }}, - rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ - rightAlignYAxis = _; - yAxis.orient( (_) ? 'right' : 'left'); - }}, - useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){ - useInteractiveGuideline = _; - if (_ === true) { - chart.interactive(false); - } - }} - }); - - nv.utils.inheritOptions(chart, bars); - nv.utils.initOptions(chart); - - return chart; - }; - - -// ohlcChart is just a historical chart with ohlc bars and some tweaks - nv.models.ohlcBarChart = function() { - var chart = nv.models.historicalBarChart(nv.models.ohlcBar()); - - // special default tooltip since we show multiple values per x - chart.useInteractiveGuideline(true); - chart.interactiveLayer.tooltip.contentGenerator(function(data) { - // we assume only one series exists for this chart - var d = data.series[0].data; - // match line colors as defined in nv.d3.css - var color = d.open < d.close ? "2ca02c" : "d62728"; - return '' + - '

    ' + data.value + '

    ' + - '' + - '' + - '' + - '' + - '' + - '
    open:' + chart.yAxis.tickFormat()(d.open) + '
    close:' + chart.yAxis.tickFormat()(d.close) + '
    high' + chart.yAxis.tickFormat()(d.high) + '
    low:' + chart.yAxis.tickFormat()(d.low) + '
    '; - }); - return chart; - }; - -// candlestickChart is just a historical chart with candlestick bars and some tweaks - nv.models.candlestickBarChart = function() { - var chart = nv.models.historicalBarChart(nv.models.candlestickBar()); - - // special default tooltip since we show multiple values per x - chart.useInteractiveGuideline(true); - chart.interactiveLayer.tooltip.contentGenerator(function(data) { - // we assume only one series exists for this chart - var d = data.series[0].data; - // match line colors as defined in nv.d3.css - var color = d.open < d.close ? "2ca02c" : "d62728"; - return '' + - '

    ' + data.value + '

    ' + - '' + - '' + - '' + - '' + - '' + - '
    open:' + chart.yAxis.tickFormat()(d.open) + '
    close:' + chart.yAxis.tickFormat()(d.close) + '
    high' + chart.yAxis.tickFormat()(d.high) + '
    low:' + chart.yAxis.tickFormat()(d.low) + '
    '; - }); - return chart; - }; - nv.models.legend = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var margin = {top: 5, right: 0, bottom: 5, left: 0} - , width = 400 - , height = 20 - , getKey = function(d) { return d.key } - , color = nv.utils.getColor() - , align = true - , padding = 32 //define how much space between legend items. - recommend 32 for furious version - , rightAlign = true - , updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch. - , radioButtonMode = false //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time) - , expanded = false - , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange') - , vers = 'classic' //Options are "classic" and "furious" - ; - - function chart(selection) { - selection.each(function(data) { - var availableWidth = width - margin.left - margin.right, - container = d3.select(this); - nv.utils.initSVG(container); - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-legend').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g'); - var g = wrap.select('g'); - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - var series = g.selectAll('.nv-series') - .data(function(d) { - if(vers != 'furious') return d; - - return d.filter(function(n) { - return expanded ? true : !n.disengaged; - }); - }); - - var seriesEnter = series.enter().append('g').attr('class', 'nv-series'); - var seriesShape; - - var versPadding; - switch(vers) { - case 'furious' : - versPadding = 23; - break; - case 'classic' : - versPadding = 20; - } - - if(vers == 'classic') { - seriesEnter.append('circle') - .style('stroke-width', 2) - .attr('class','nv-legend-symbol') - .attr('r', 5); - - seriesShape = series.select('circle'); - } else if (vers == 'furious') { - seriesEnter.append('rect') - .style('stroke-width', 2) - .attr('class','nv-legend-symbol') - .attr('rx', 3) - .attr('ry', 3); - - seriesShape = series.select('.nv-legend-symbol'); - - seriesEnter.append('g') - .attr('class', 'nv-check-box') - .property('innerHTML','') - .attr('transform', 'translate(-10,-8)scale(0.5)'); - - var seriesCheckbox = series.select('.nv-check-box'); - - seriesCheckbox.each(function(d,i) { - d3.select(this).selectAll('path') - .attr('stroke', setTextColor(d,i)); - }); - } - - seriesEnter.append('text') - .attr('text-anchor', 'start') - .attr('class','nv-legend-text') - .attr('dy', '.32em') - .attr('dx', '8'); - - var seriesText = series.select('text.nv-legend-text'); - - series - .on('mouseover', function(d,i) { - dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects - }) - .on('mouseout', function(d,i) { - dispatch.legendMouseout(d,i); - }) - .on('click', function(d,i) { - dispatch.legendClick(d,i); - // make sure we re-get data in case it was modified - var data = series.data(); - if (updateState) { - if(vers =='classic') { - if (radioButtonMode) { - //Radio button mode: set every series to disabled, - // and enable the clicked series. - data.forEach(function(series) { series.disabled = true}); - d.disabled = false; - } - else { - d.disabled = !d.disabled; - if (data.every(function(series) { return series.disabled})) { - //the default behavior of NVD3 legends is, if every single series - // is disabled, turn all series' back on. - data.forEach(function(series) { series.disabled = false}); - } - } - } else if(vers == 'furious') { - if(expanded) { - d.disengaged = !d.disengaged; - d.userDisabled = d.userDisabled == undefined ? !!d.disabled : d.userDisabled; - d.disabled = d.disengaged || d.userDisabled; - } else if (!expanded) { - d.disabled = !d.disabled; - d.userDisabled = d.disabled; - var engaged = data.filter(function(d) { return !d.disengaged; }); - if (engaged.every(function(series) { return series.userDisabled })) { - //the default behavior of NVD3 legends is, if every single series - // is disabled, turn all series' back on. - data.forEach(function(series) { - series.disabled = series.userDisabled = false; - }); - } - } - } - dispatch.stateChange({ - disabled: data.map(function(d) { return !!d.disabled }), - disengaged: data.map(function(d) { return !!d.disengaged }) - }); - - } - }) - .on('dblclick', function(d,i) { - if(vers == 'furious' && expanded) return; - dispatch.legendDblclick(d,i); - if (updateState) { - // make sure we re-get data in case it was modified - var data = series.data(); - //the default behavior of NVD3 legends, when double clicking one, - // is to set all other series' to false, and make the double clicked series enabled. - data.forEach(function(series) { - series.disabled = true; - if(vers == 'furious') series.userDisabled = series.disabled; - }); - d.disabled = false; - if(vers == 'furious') d.userDisabled = d.disabled; - dispatch.stateChange({ - disabled: data.map(function(d) { return !!d.disabled }) - }); - } - }); - - series.classed('nv-disabled', function(d) { return d.userDisabled }); - series.exit().remove(); - - seriesText - .attr('fill', setTextColor) - .text(getKey); - - //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option) - // NEW ALIGNING CODE, TODO: clean up - var legendWidth = 0; - if (align) { - - var seriesWidths = []; - series.each(function(d,i) { - var legendText = d3.select(this).select('text'); - var nodeTextLength; - try { - nodeTextLength = legendText.node().getComputedTextLength(); - // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead - if(nodeTextLength <= 0) throw Error(); - } - catch(e) { - nodeTextLength = nv.utils.calcApproxTextWidth(legendText); - } - - seriesWidths.push(nodeTextLength + padding); - }); - - var seriesPerRow = 0; - var columnWidths = []; - legendWidth = 0; - - while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) { - columnWidths[seriesPerRow] = seriesWidths[seriesPerRow]; - legendWidth += seriesWidths[seriesPerRow++]; - } - if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row - - while ( legendWidth > availableWidth && seriesPerRow > 1 ) { - columnWidths = []; - seriesPerRow--; - - for (var k = 0; k < seriesWidths.length; k++) { - if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) ) - columnWidths[k % seriesPerRow] = seriesWidths[k]; - } - - legendWidth = columnWidths.reduce(function(prev, cur, index, array) { - return prev + cur; - }); - } - - var xPositions = []; - for (var i = 0, curX = 0; i < seriesPerRow; i++) { - xPositions[i] = curX; - curX += columnWidths[i]; - } - - series - .attr('transform', function(d, i) { - return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')'; - }); - - //position legend as far right as possible within the total width - if (rightAlign) { - g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')'); - } - else { - g.attr('transform', 'translate(0' + ',' + margin.top + ')'); - } - - height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding); - - } else { - - var ypos = 5, - newxpos = 5, - maxwidth = 0, - xpos; - series - .attr('transform', function(d, i) { - var length = d3.select(this).select('text').node().getComputedTextLength() + padding; - xpos = newxpos; - - if (width < margin.left + margin.right + xpos + length) { - newxpos = xpos = 5; - ypos += versPadding; - } - - newxpos += length; - if (newxpos > maxwidth) maxwidth = newxpos; - - if(legendWidth < xpos + maxwidth) { - legendWidth = xpos + maxwidth; - } - return 'translate(' + xpos + ',' + ypos + ')'; - }); - - //position legend as far right as possible within the total width - g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')'); - - height = margin.top + margin.bottom + ypos + 15; - } - - if(vers == 'furious') { - // Size rectangles after text is placed - seriesShape - .attr('width', function(d,i) { - return seriesText[0][i].getComputedTextLength() + 27; - }) - .attr('height', 18) - .attr('y', -9) - .attr('x', -15); - - // The background for the expanded legend (UI) - gEnter.insert('rect',':first-child') - .attr('class', 'nv-legend-bg') - .attr('fill', '#eee') - // .attr('stroke', '#444') - .attr('opacity',0); - - var seriesBG = g.select('.nv-legend-bg'); - - seriesBG - .transition().duration(300) - .attr('x', -versPadding ) - .attr('width', legendWidth + versPadding - 12) - .attr('height', height + 10) - .attr('y', -margin.top - 10) - .attr('opacity', expanded ? 1 : 0); - - - } - - seriesShape - .style('fill', setBGColor) - .style('fill-opacity', setBGOpacity) - .style('stroke', setBGColor); - }); - - function setTextColor(d,i) { - if(vers != 'furious') return '#000'; - if(expanded) { - return d.disengaged ? '#000' : '#fff'; - } else if (!expanded) { - if(!d.color) d.color = color(d,i); - return !!d.disabled ? d.color : '#fff'; - } - } - - function setBGColor(d,i) { - if(expanded && vers == 'furious') { - return d.disengaged ? '#eee' : d.color || color(d,i); - } else { - return d.color || color(d,i); - } - } - - - function setBGOpacity(d,i) { - if(expanded && vers == 'furious') { - return 1; - } else { - return !!d.disabled ? 0 : 1; - } - } - - return chart; - } - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - key: {get: function(){return getKey;}, set: function(_){getKey=_;}}, - align: {get: function(){return align;}, set: function(_){align=_;}}, - rightAlign: {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}}, - padding: {get: function(){return padding;}, set: function(_){padding=_;}}, - updateState: {get: function(){return updateState;}, set: function(_){updateState=_;}}, - radioButtonMode: {get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}}, - expanded: {get: function(){return expanded;}, set: function(_){expanded=_;}}, - vers: {get: function(){return vers;}, set: function(_){vers=_;}}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - }} - }); - - nv.utils.initOptions(chart); - - return chart; - }; - - nv.models.line = function() { - "use strict"; - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var scatter = nv.models.scatter() - ; - - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = 960 - , height = 500 - , container = null - , strokeWidth = 1.5 - , color = nv.utils.defaultColor() // a function that returns a color - , getX = function(d) { return d.x } // accessor to get the x value from a data point - , getY = function(d) { return d.y } // accessor to get the y value from a data point - , defined = function(d,i) { return !isNaN(getY(d,i)) && getY(d,i) !== null } // allows a line to be not continuous when it is not defined - , isArea = function(d) { return d.area } // decides if a line is an area or just a line - , clipEdge = false // if true, masks lines within x and y scale - , x //can be accessed via chart.xScale() - , y //can be accessed via chart.yScale() - , interpolate = "linear" // controls the line interpolation - , duration = 250 - , dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout', 'renderEnd') - ; - - scatter - .pointSize(16) // default size - .pointDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor - ; - - //============================================================ - - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - var x0, y0 //used to store previous scales - , renderWatch = nv.utils.renderWatch(dispatch, duration) - ; - - //============================================================ - - - function chart(selection) { - renderWatch.reset(); - renderWatch.models(scatter); - selection.each(function(data) { - container = d3.select(this); - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); - nv.utils.initSVG(container); - - // Setup Scales - x = scatter.xScale(); - y = scatter.yScale(); - - x0 = x0 || x; - y0 = y0 || y; - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line'); - var defsEnter = wrapEnter.append('defs'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); - - gEnter.append('g').attr('class', 'nv-groups'); - gEnter.append('g').attr('class', 'nv-scatterWrap'); - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - scatter - .width(availableWidth) - .height(availableHeight); - - var scatterWrap = wrap.select('.nv-scatterWrap'); - scatterWrap.call(scatter); - - defsEnter.append('clipPath') - .attr('id', 'nv-edge-clip-' + scatter.id()) - .append('rect'); - - wrap.select('#nv-edge-clip-' + scatter.id() + ' rect') - .attr('width', availableWidth) - .attr('height', (availableHeight > 0) ? availableHeight : 0); - - g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : ''); - scatterWrap - .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : ''); - - var groups = wrap.select('.nv-groups').selectAll('.nv-group') - .data(function(d) { return d }, function(d) { return d.key }); - groups.enter().append('g') - .style('stroke-opacity', 1e-6) - .style('stroke-width', function(d) { return d.strokeWidth || strokeWidth }) - .style('fill-opacity', 1e-6); - - groups.exit().remove(); - - groups - .attr('class', function(d,i) { - return (d.classed || '') + ' nv-group nv-series-' + i; - }) - .classed('hover', function(d) { return d.hover }) - .style('fill', function(d,i){ return color(d, i) }) - .style('stroke', function(d,i){ return color(d, i)}); - groups.watchTransition(renderWatch, 'line: groups') - .style('stroke-opacity', 1) - .style('fill-opacity', function(d) { return d.fillOpacity || .5}); - - var areaPaths = groups.selectAll('path.nv-area') - .data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area - areaPaths.enter().append('path') - .attr('class', 'nv-area') - .attr('d', function(d) { - return d3.svg.area() - .interpolate(interpolate) - .defined(defined) - .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) }) - .y0(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) }) - .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) }) - //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this - .apply(this, [d.values]) - }); - groups.exit().selectAll('path.nv-area') - .remove(); - - areaPaths.watchTransition(renderWatch, 'line: areaPaths') - .attr('d', function(d) { - return d3.svg.area() - .interpolate(interpolate) - .defined(defined) - .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) }) - .y0(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) }) - .y1(function(d,i) { return y( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) }) - //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this - .apply(this, [d.values]) - }); - - var linePaths = groups.selectAll('path.nv-line') - .data(function(d) { return [d.values] }); - - linePaths.enter().append('path') - .attr('class', 'nv-line') - .attr('d', - d3.svg.line() - .interpolate(interpolate) - .defined(defined) - .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) }) - .y(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) }) - ); - - linePaths.watchTransition(renderWatch, 'line: linePaths') - .attr('d', - d3.svg.line() - .interpolate(interpolate) - .defined(defined) - .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) }) - .y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) }) - ); - - //store old scales for use in transitions on update - x0 = x.copy(); - y0 = y.copy(); - }); - renderWatch.renderEnd('line immediate'); - return chart; - } - - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - chart.dispatch = dispatch; - chart.scatter = scatter; - // Pass through events - scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); }); - scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); }); - scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); }); - - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - defined: {get: function(){return defined;}, set: function(_){defined=_;}}, - interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}}, - clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - duration: {get: function(){return duration;}, set: function(_){ - duration = _; - renderWatch.reset(duration); - scatter.duration(duration); - }}, - isArea: {get: function(){return isArea;}, set: function(_){ - isArea = d3.functor(_); - }}, - x: {get: function(){return getX;}, set: function(_){ - getX = _; - scatter.x(_); - }}, - y: {get: function(){return getY;}, set: function(_){ - getY = _; - scatter.y(_); - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - scatter.color(color); - }} - }); - - nv.utils.inheritOptions(chart, scatter); - nv.utils.initOptions(chart); - - return chart; - }; - nv.models.lineChart = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var lines = nv.models.line() - , xAxis = nv.models.axis() - , yAxis = nv.models.axis() - , legend = nv.models.legend() - , interactiveLayer = nv.interactiveGuideline() - , tooltip = nv.models.tooltip() - ; - - var margin = {top: 30, right: 20, bottom: 50, left: 60} - , color = nv.utils.defaultColor() - , width = null - , height = null - , showLegend = true - , showXAxis = true - , showYAxis = true - , rightAlignYAxis = false - , useInteractiveGuideline = false - , x - , y - , state = nv.utils.state() - , defaultState = null - , noData = null - , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd') - , duration = 250 - ; - - // set options on sub-objects for this chart - xAxis.orient('bottom').tickPadding(7); - yAxis.orient(rightAlignYAxis ? 'right' : 'left'); - tooltip.valueFormatter(function(d, i) { - return yAxis.tickFormat()(d, i); - }).headerFormatter(function(d, i) { - return xAxis.tickFormat()(d, i); - }); - - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - var renderWatch = nv.utils.renderWatch(dispatch, duration); - - var stateGetter = function(data) { - return function(){ - return { - active: data.map(function(d) { return !d.disabled }) - }; - } - }; - - var stateSetter = function(data) { - return function(state) { - if (state.active !== undefined) - data.forEach(function(series,i) { - series.disabled = !state.active[i]; - }); - } - }; - - function chart(selection) { - renderWatch.reset(); - renderWatch.models(lines); - if (showXAxis) renderWatch.models(xAxis); - if (showYAxis) renderWatch.models(yAxis); - - selection.each(function(data) { - var container = d3.select(this), - that = this; - nv.utils.initSVG(container); - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); - - chart.update = function() { - if (duration === 0) - container.call(chart); - else - container.transition().duration(duration).call(chart) - }; - chart.container = this; - - state - .setter(stateSetter(data), chart.update) - .getter(stateGetter(data)) - .update(); - - // DEPRECATED set state.disableddisabled - state.disabled = data.map(function(d) { return !!d.disabled }); - - if (!defaultState) { - var key; - defaultState = {}; - for (key in state) { - if (state[key] instanceof Array) - defaultState[key] = state[key].slice(0); - else - defaultState[key] = state[key]; - } - } - - // Display noData message if there's nothing to show. - if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { - nv.utils.noData(chart, container) - return chart; - } else { - container.selectAll('.nv-noData').remove(); - } - - - // Setup Scales - x = lines.xScale(); - y = lines.yScale(); - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g'); - var g = wrap.select('g'); - - gEnter.append("rect").style("opacity",0); - gEnter.append('g').attr('class', 'nv-x nv-axis'); - gEnter.append('g').attr('class', 'nv-y nv-axis'); - gEnter.append('g').attr('class', 'nv-linesWrap'); - gEnter.append('g').attr('class', 'nv-legendWrap'); - gEnter.append('g').attr('class', 'nv-interactive'); - - g.select("rect") - .attr("width",availableWidth) - .attr("height",(availableHeight > 0) ? availableHeight : 0); - - // Legend - if (showLegend) { - legend.width(availableWidth); - - g.select('.nv-legendWrap') - .datum(data) - .call(legend); - - if ( margin.top != legend.height()) { - margin.top = legend.height(); - availableHeight = nv.utils.availableHeight(height, container, margin); - } - - wrap.select('.nv-legendWrap') - .attr('transform', 'translate(0,' + (-margin.top) +')') - } - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - if (rightAlignYAxis) { - g.select(".nv-y.nv-axis") - .attr("transform", "translate(" + availableWidth + ",0)"); - } - - //Set up interactive layer - if (useInteractiveGuideline) { - interactiveLayer - .width(availableWidth) - .height(availableHeight) - .margin({left:margin.left, top:margin.top}) - .svgContainer(container) - .xScale(x); - wrap.select(".nv-interactive").call(interactiveLayer); - } - - lines - .width(availableWidth) - .height(availableHeight) - .color(data.map(function(d,i) { - return d.color || color(d, i); - }).filter(function(d,i) { return !data[i].disabled })); - - - var linesWrap = g.select('.nv-linesWrap') - .datum(data.filter(function(d) { return !d.disabled })); - - linesWrap.call(lines); - - // Setup Axes - if (showXAxis) { - xAxis - .scale(x) - ._ticks(nv.utils.calcTicksX(availableWidth/100, data) ) - .tickSize(-availableHeight, 0); - - g.select('.nv-x.nv-axis') - .attr('transform', 'translate(0,' + y.range()[0] + ')'); - g.select('.nv-x.nv-axis') - .call(xAxis); - } - - if (showYAxis) { - yAxis - .scale(y) - ._ticks(nv.utils.calcTicksY(availableHeight/36, data) ) - .tickSize( -availableWidth, 0); - - g.select('.nv-y.nv-axis') - .call(yAxis); - } - - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ - - legend.dispatch.on('stateChange', function(newState) { - for (var key in newState) - state[key] = newState[key]; - dispatch.stateChange(state); - chart.update(); - }); - - interactiveLayer.dispatch.on('elementMousemove', function(e) { - lines.clearHighlights(); - var singlePoint, pointIndex, pointXLocation, allData = []; - data - .filter(function(series, i) { - series.seriesIndex = i; - return !series.disabled; - }) - .forEach(function(series,i) { - pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x()); - var point = series.values[pointIndex]; - var pointYValue = chart.y()(point, pointIndex); - if (pointYValue != null) { - lines.highlightPoint(i, pointIndex, true); - } - if (point === undefined) return; - if (singlePoint === undefined) singlePoint = point; - if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex)); - allData.push({ - key: series.key, - value: pointYValue, - color: color(series,series.seriesIndex) - }); - }); - //Highlight the tooltip entry based on which point the mouse is closest to. - if (allData.length > 2) { - var yValue = chart.yScale().invert(e.mouseY); - var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]); - var threshold = 0.03 * domainExtent; - var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold); - if (indexToHighlight !== null) - allData[indexToHighlight].highlight = true; - } - - var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex)); - interactiveLayer.tooltip - .position({left: e.mouseX + margin.left, top: e.mouseY + margin.top}) - .chartContainer(that.parentNode) - .valueFormatter(function(d,i) { - return d == null ? "N/A" : yAxis.tickFormat()(d); - }) - .data({ - value: xValue, - index: pointIndex, - series: allData - })(); - - interactiveLayer.renderGuideLine(pointXLocation); - - }); - - interactiveLayer.dispatch.on('elementClick', function(e) { - var pointXLocation, allData = []; - - data.filter(function(series, i) { - series.seriesIndex = i; - return !series.disabled; - }).forEach(function(series) { - var pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x()); - var point = series.values[pointIndex]; - if (typeof point === 'undefined') return; - if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex)); - var yPos = chart.yScale()(chart.y()(point,pointIndex)); - allData.push({ - point: point, - pointIndex: pointIndex, - pos: [pointXLocation, yPos], - seriesIndex: series.seriesIndex, - series: series - }); - }); - - lines.dispatch.elementClick(allData); - }); - - interactiveLayer.dispatch.on("elementMouseout",function(e) { - lines.clearHighlights(); - }); - - dispatch.on('changeState', function(e) { - if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) { - data.forEach(function(series,i) { - series.disabled = e.disabled[i]; - }); - - state.disabled = e.disabled; - } - - chart.update(); - }); - - }); - - renderWatch.renderEnd('lineChart immediate'); - return chart; - } - - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ - - lines.dispatch.on('elementMouseover.tooltip', function(evt) { - tooltip.data(evt).position(evt.pos).hidden(false); - }); - - lines.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true) - }); - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - // expose chart's sub-components - chart.dispatch = dispatch; - chart.lines = lines; - chart.legend = legend; - chart.xAxis = xAxis; - chart.yAxis = yAxis; - chart.interactiveLayer = interactiveLayer; - chart.tooltip = tooltip; - - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, - showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, - showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, - defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, - noData: {get: function(){return noData;}, set: function(_){noData=_;}}, - - // deprecated options - tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); - tooltip.enabled(!!_); - }}, - tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); - tooltip.contentGenerator(_); - }}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - duration: {get: function(){return duration;}, set: function(_){ - duration = _; - renderWatch.reset(duration); - lines.duration(duration); - xAxis.duration(duration); - yAxis.duration(duration); - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - legend.color(color); - lines.color(color); - }}, - rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ - rightAlignYAxis = _; - yAxis.orient( rightAlignYAxis ? 'right' : 'left'); - }}, - useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){ - useInteractiveGuideline = _; - if (useInteractiveGuideline) { - lines.interactive(false); - lines.useVoronoi(false); - } - }} - }); - - nv.utils.inheritOptions(chart, lines); - nv.utils.initOptions(chart); - - return chart; - }; - nv.models.linePlusBarChart = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var lines = nv.models.line() - , lines2 = nv.models.line() - , bars = nv.models.historicalBar() - , bars2 = nv.models.historicalBar() - , xAxis = nv.models.axis() - , x2Axis = nv.models.axis() - , y1Axis = nv.models.axis() - , y2Axis = nv.models.axis() - , y3Axis = nv.models.axis() - , y4Axis = nv.models.axis() - , legend = nv.models.legend() - , brush = d3.svg.brush() - , tooltip = nv.models.tooltip() - ; - - var margin = {top: 30, right: 30, bottom: 30, left: 60} - , margin2 = {top: 0, right: 30, bottom: 20, left: 60} - , width = null - , height = null - , getX = function(d) { return d.x } - , getY = function(d) { return d.y } - , color = nv.utils.defaultColor() - , showLegend = true - , focusEnable = true - , focusShowAxisY = false - , focusShowAxisX = true - , focusHeight = 50 - , extent - , brushExtent = null - , x - , x2 - , y1 - , y2 - , y3 - , y4 - , noData = null - , dispatch = d3.dispatch('brush', 'stateChange', 'changeState') - , transitionDuration = 0 - , state = nv.utils.state() - , defaultState = null - , legendLeftAxisHint = ' (left axis)' - , legendRightAxisHint = ' (right axis)' - ; - - lines.clipEdge(true); - lines2.interactive(false); - // We don't want any points emitted for the focus chart's scatter graph. - lines2.pointActive(function(d) { return false }); - xAxis.orient('bottom').tickPadding(5); - y1Axis.orient('left'); - y2Axis.orient('right'); - x2Axis.orient('bottom').tickPadding(5); - y3Axis.orient('left'); - y4Axis.orient('right'); - - tooltip.headerEnabled(true).headerFormatter(function(d, i) { - return xAxis.tickFormat()(d, i); - }); - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - var stateGetter = function(data) { - return function(){ - return { - active: data.map(function(d) { return !d.disabled }) - }; - } - }; - - var stateSetter = function(data) { - return function(state) { - if (state.active !== undefined) - data.forEach(function(series,i) { - series.disabled = !state.active[i]; - }); - } - }; - - function chart(selection) { - selection.each(function(data) { - var container = d3.select(this), - that = this; - nv.utils.initSVG(container); - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight1 = nv.utils.availableHeight(height, container, margin) - - (focusEnable ? focusHeight : 0), - availableHeight2 = focusHeight - margin2.top - margin2.bottom; - - chart.update = function() { container.transition().duration(transitionDuration).call(chart); }; - chart.container = this; - - state - .setter(stateSetter(data), chart.update) - .getter(stateGetter(data)) - .update(); - - // DEPRECATED set state.disableddisabled - state.disabled = data.map(function(d) { return !!d.disabled }); - - if (!defaultState) { - var key; - defaultState = {}; - for (key in state) { - if (state[key] instanceof Array) - defaultState[key] = state[key].slice(0); - else - defaultState[key] = state[key]; - } - } - - // Display No Data message if there's nothing to show. - if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { - nv.utils.noData(chart, container) - return chart; - } else { - container.selectAll('.nv-noData').remove(); - } - - // Setup Scales - var dataBars = data.filter(function(d) { return !d.disabled && d.bar }); - var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240 - - x = bars.xScale(); - x2 = x2Axis.scale(); - y1 = bars.yScale(); - y2 = lines.yScale(); - y3 = bars2.yScale(); - y4 = lines2.yScale(); - - var series1 = data - .filter(function(d) { return !d.disabled && d.bar }) - .map(function(d) { - return d.values.map(function(d,i) { - return { x: getX(d,i), y: getY(d,i) } - }) - }); - - var series2 = data - .filter(function(d) { return !d.disabled && !d.bar }) - .map(function(d) { - return d.values.map(function(d,i) { - return { x: getX(d,i), y: getY(d,i) } - }) - }); - - x.range([0, availableWidth]); - - x2 .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } )) - .range([0, availableWidth]); - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g'); - var g = wrap.select('g'); - - gEnter.append('g').attr('class', 'nv-legendWrap'); - - // this is the main chart - var focusEnter = gEnter.append('g').attr('class', 'nv-focus'); - focusEnter.append('g').attr('class', 'nv-x nv-axis'); - focusEnter.append('g').attr('class', 'nv-y1 nv-axis'); - focusEnter.append('g').attr('class', 'nv-y2 nv-axis'); - focusEnter.append('g').attr('class', 'nv-barsWrap'); - focusEnter.append('g').attr('class', 'nv-linesWrap'); - - // context chart is where you can focus in - var contextEnter = gEnter.append('g').attr('class', 'nv-context'); - contextEnter.append('g').attr('class', 'nv-x nv-axis'); - contextEnter.append('g').attr('class', 'nv-y1 nv-axis'); - contextEnter.append('g').attr('class', 'nv-y2 nv-axis'); - contextEnter.append('g').attr('class', 'nv-barsWrap'); - contextEnter.append('g').attr('class', 'nv-linesWrap'); - contextEnter.append('g').attr('class', 'nv-brushBackground'); - contextEnter.append('g').attr('class', 'nv-x nv-brush'); - - //============================================================ - // Legend - //------------------------------------------------------------ - - if (showLegend) { - var legendWidth = legend.align() ? availableWidth / 2 : availableWidth; - var legendXPosition = legend.align() ? legendWidth : 0; - - legend.width(legendWidth); - - g.select('.nv-legendWrap') - .datum(data.map(function(series) { - series.originalKey = series.originalKey === undefined ? series.key : series.originalKey; - series.key = series.originalKey + (series.bar ? legendLeftAxisHint : legendRightAxisHint); - return series; - })) - .call(legend); - - if ( margin.top != legend.height()) { - margin.top = legend.height(); - // FIXME: shouldn't this be "- (focusEnabled ? focusHeight : 0)"? - availableHeight1 = nv.utils.availableHeight(height, container, margin) - focusHeight; - } - - g.select('.nv-legendWrap') - .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')'); - } - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - //============================================================ - // Context chart (focus chart) components - //------------------------------------------------------------ - - // hide or show the focus context chart - g.select('.nv-context').style('display', focusEnable ? 'initial' : 'none'); - - bars2 - .width(availableWidth) - .height(availableHeight2) - .color(data.map(function (d, i) { - return d.color || color(d, i); - }).filter(function (d, i) { - return !data[i].disabled && data[i].bar - })); - lines2 - .width(availableWidth) - .height(availableHeight2) - .color(data.map(function (d, i) { - return d.color || color(d, i); - }).filter(function (d, i) { - return !data[i].disabled && !data[i].bar - })); - - var bars2Wrap = g.select('.nv-context .nv-barsWrap') - .datum(dataBars.length ? dataBars : [ - {values: []} - ]); - var lines2Wrap = g.select('.nv-context .nv-linesWrap') - .datum(!dataLines[0].disabled ? dataLines : [ - {values: []} - ]); - - g.select('.nv-context') - .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')'); - - bars2Wrap.transition().call(bars2); - lines2Wrap.transition().call(lines2); - - // context (focus chart) axis controls - if (focusShowAxisX) { - x2Axis - ._ticks( nv.utils.calcTicksX(availableWidth / 100, data)) - .tickSize(-availableHeight2, 0); - g.select('.nv-context .nv-x.nv-axis') - .attr('transform', 'translate(0,' + y3.range()[0] + ')'); - g.select('.nv-context .nv-x.nv-axis').transition() - .call(x2Axis); - } - - if (focusShowAxisY) { - y3Axis - .scale(y3) - ._ticks( availableHeight2 / 36 ) - .tickSize( -availableWidth, 0); - y4Axis - .scale(y4) - ._ticks( availableHeight2 / 36 ) - .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none - - g.select('.nv-context .nv-y3.nv-axis') - .style('opacity', dataBars.length ? 1 : 0) - .attr('transform', 'translate(0,' + x2.range()[0] + ')'); - g.select('.nv-context .nv-y2.nv-axis') - .style('opacity', dataLines.length ? 1 : 0) - .attr('transform', 'translate(' + x2.range()[1] + ',0)'); - - g.select('.nv-context .nv-y1.nv-axis').transition() - .call(y3Axis); - g.select('.nv-context .nv-y2.nv-axis').transition() - .call(y4Axis); - } - - // Setup Brush - brush.x(x2).on('brush', onBrush); - - if (brushExtent) brush.extent(brushExtent); - - var brushBG = g.select('.nv-brushBackground').selectAll('g') - .data([brushExtent || brush.extent()]); - - var brushBGenter = brushBG.enter() - .append('g'); - - brushBGenter.append('rect') - .attr('class', 'left') - .attr('x', 0) - .attr('y', 0) - .attr('height', availableHeight2); - - brushBGenter.append('rect') - .attr('class', 'right') - .attr('x', 0) - .attr('y', 0) - .attr('height', availableHeight2); - - var gBrush = g.select('.nv-x.nv-brush') - .call(brush); - gBrush.selectAll('rect') - //.attr('y', -5) - .attr('height', availableHeight2); - gBrush.selectAll('.resize').append('path').attr('d', resizePath); - - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ - - legend.dispatch.on('stateChange', function(newState) { - for (var key in newState) - state[key] = newState[key]; - dispatch.stateChange(state); - chart.update(); - }); - - // Update chart from a state object passed to event handler - dispatch.on('changeState', function(e) { - if (typeof e.disabled !== 'undefined') { - data.forEach(function(series,i) { - series.disabled = e.disabled[i]; - }); - state.disabled = e.disabled; - } - chart.update(); - }); - - //============================================================ - // Functions - //------------------------------------------------------------ - - // Taken from crossfilter (http://square.github.com/crossfilter/) - function resizePath(d) { - var e = +(d == 'e'), - x = e ? 1 : -1, - y = availableHeight2 / 3; - return 'M' + (.5 * x) + ',' + y - + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6) - + 'V' + (2 * y - 6) - + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y) - + 'Z' - + 'M' + (2.5 * x) + ',' + (y + 8) - + 'V' + (2 * y - 8) - + 'M' + (4.5 * x) + ',' + (y + 8) - + 'V' + (2 * y - 8); - } - - - function updateBrushBG() { - if (!brush.empty()) brush.extent(brushExtent); - brushBG - .data([brush.empty() ? x2.domain() : brushExtent]) - .each(function(d,i) { - var leftWidth = x2(d[0]) - x2.range()[0], - rightWidth = x2.range()[1] - x2(d[1]); - d3.select(this).select('.left') - .attr('width', leftWidth < 0 ? 0 : leftWidth); - - d3.select(this).select('.right') - .attr('x', x2(d[1])) - .attr('width', rightWidth < 0 ? 0 : rightWidth); - }); - } - - function onBrush() { - brushExtent = brush.empty() ? null : brush.extent(); - extent = brush.empty() ? x2.domain() : brush.extent(); - dispatch.brush({extent: extent, brush: brush}); - updateBrushBG(); - - // Prepare Main (Focus) Bars and Lines - bars - .width(availableWidth) - .height(availableHeight1) - .color(data.map(function(d,i) { - return d.color || color(d, i); - }).filter(function(d,i) { return !data[i].disabled && data[i].bar })); - - lines - .width(availableWidth) - .height(availableHeight1) - .color(data.map(function(d,i) { - return d.color || color(d, i); - }).filter(function(d,i) { return !data[i].disabled && !data[i].bar })); - - var focusBarsWrap = g.select('.nv-focus .nv-barsWrap') - .datum(!dataBars.length ? [{values:[]}] : - dataBars - .map(function(d,i) { - return { - key: d.key, - values: d.values.filter(function(d,i) { - return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1]; - }) - } - }) - ); - - var focusLinesWrap = g.select('.nv-focus .nv-linesWrap') - .datum(dataLines[0].disabled ? [{values:[]}] : - dataLines - .map(function(d,i) { - return { - area: d.area, - fillOpacity: d.fillOpacity, - key: d.key, - values: d.values.filter(function(d,i) { - return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1]; - }) - } - }) - ); - - // Update Main (Focus) X Axis - if (dataBars.length) { - x = bars.xScale(); - } else { - x = lines.xScale(); - } - - xAxis - .scale(x) - ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) - .tickSize(-availableHeight1, 0); - - xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]); - - g.select('.nv-x.nv-axis').transition().duration(transitionDuration) - .call(xAxis); - - // Update Main (Focus) Bars and Lines - focusBarsWrap.transition().duration(transitionDuration).call(bars); - focusLinesWrap.transition().duration(transitionDuration).call(lines); - - // Setup and Update Main (Focus) Y Axes - g.select('.nv-focus .nv-x.nv-axis') - .attr('transform', 'translate(0,' + y1.range()[0] + ')'); - - y1Axis - .scale(y1) - ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) ) - .tickSize(-availableWidth, 0); - y2Axis - .scale(y2) - ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) ) - .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none - - g.select('.nv-focus .nv-y1.nv-axis') - .style('opacity', dataBars.length ? 1 : 0); - g.select('.nv-focus .nv-y2.nv-axis') - .style('opacity', dataLines.length && !dataLines[0].disabled ? 1 : 0) - .attr('transform', 'translate(' + x.range()[1] + ',0)'); - - g.select('.nv-focus .nv-y1.nv-axis').transition().duration(transitionDuration) - .call(y1Axis); - g.select('.nv-focus .nv-y2.nv-axis').transition().duration(transitionDuration) - .call(y2Axis); - } - - onBrush(); - - }); - - return chart; - } - - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ - - lines.dispatch.on('elementMouseover.tooltip', function(evt) { - tooltip - .duration(100) - .valueFormatter(function(d, i) { - return y2Axis.tickFormat()(d, i); - }) - .data(evt) - .position(evt.pos) - .hidden(false); - }); - - lines.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true) - }); - - bars.dispatch.on('elementMouseover.tooltip', function(evt) { - evt.value = chart.x()(evt.data); - evt['series'] = { - value: chart.y()(evt.data), - color: evt.color - }; - tooltip - .duration(0) - .valueFormatter(function(d, i) { - return y1Axis.tickFormat()(d, i); - }) - .data(evt) - .hidden(false); - }); - - bars.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true); - }); - - bars.dispatch.on('elementMousemove.tooltip', function(evt) { - tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); - }); - - //============================================================ - - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - // expose chart's sub-components - chart.dispatch = dispatch; - chart.legend = legend; - chart.lines = lines; - chart.lines2 = lines2; - chart.bars = bars; - chart.bars2 = bars2; - chart.xAxis = xAxis; - chart.x2Axis = x2Axis; - chart.y1Axis = y1Axis; - chart.y2Axis = y2Axis; - chart.y3Axis = y3Axis; - chart.y4Axis = y4Axis; - chart.tooltip = tooltip; - - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, - brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}}, - noData: {get: function(){return noData;}, set: function(_){noData=_;}}, - focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}}, - focusHeight: {get: function(){return focusHeight;}, set: function(_){focusHeight=_;}}, - focusShowAxisX: {get: function(){return focusShowAxisX;}, set: function(_){focusShowAxisX=_;}}, - focusShowAxisY: {get: function(){return focusShowAxisY;}, set: function(_){focusShowAxisY=_;}}, - legendLeftAxisHint: {get: function(){return legendLeftAxisHint;}, set: function(_){legendLeftAxisHint=_;}}, - legendRightAxisHint: {get: function(){return legendRightAxisHint;}, set: function(_){legendRightAxisHint=_;}}, - - // deprecated options - tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); - tooltip.enabled(!!_); - }}, - tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); - tooltip.contentGenerator(_); - }}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - focusMargin: {get: function(){return margin2;}, set: function(_){ - margin2.top = _.top !== undefined ? _.top : margin2.top; - margin2.right = _.right !== undefined ? _.right : margin2.right; - margin2.bottom = _.bottom !== undefined ? _.bottom : margin2.bottom; - margin2.left = _.left !== undefined ? _.left : margin2.left; - }}, - duration: {get: function(){return transitionDuration;}, set: function(_){ - transitionDuration = _; - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - legend.color(color); - }}, - x: {get: function(){return getX;}, set: function(_){ - getX = _; - lines.x(_); - lines2.x(_); - bars.x(_); - bars2.x(_); - }}, - y: {get: function(){return getY;}, set: function(_){ - getY = _; - lines.y(_); - lines2.y(_); - bars.y(_); - bars2.y(_); - }} - }); - - nv.utils.inheritOptions(chart, lines); - nv.utils.initOptions(chart); - - return chart; - }; - nv.models.lineWithFocusChart = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var lines = nv.models.line() - , lines2 = nv.models.line() - , xAxis = nv.models.axis() - , yAxis = nv.models.axis() - , x2Axis = nv.models.axis() - , y2Axis = nv.models.axis() - , legend = nv.models.legend() - , brush = d3.svg.brush() - , tooltip = nv.models.tooltip() - , interactiveLayer = nv.interactiveGuideline() - ; - - var margin = {top: 30, right: 30, bottom: 30, left: 60} - , margin2 = {top: 0, right: 30, bottom: 20, left: 60} - , color = nv.utils.defaultColor() - , width = null - , height = null - , height2 = 50 - , useInteractiveGuideline = false - , x - , y - , x2 - , y2 - , showLegend = true - , brushExtent = null - , noData = null - , dispatch = d3.dispatch('brush', 'stateChange', 'changeState') - , transitionDuration = 250 - , state = nv.utils.state() - , defaultState = null - ; - - lines.clipEdge(true).duration(0); - lines2.interactive(false); - // We don't want any points emitted for the focus chart's scatter graph. - lines2.pointActive(function(d) { return false }); - xAxis.orient('bottom').tickPadding(5); - yAxis.orient('left'); - x2Axis.orient('bottom').tickPadding(5); - y2Axis.orient('left'); - - tooltip.valueFormatter(function(d, i) { - return yAxis.tickFormat()(d, i); - }).headerFormatter(function(d, i) { - return xAxis.tickFormat()(d, i); - }); - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - var stateGetter = function(data) { - return function(){ - return { - active: data.map(function(d) { return !d.disabled }) - }; - } - }; - - var stateSetter = function(data) { - return function(state) { - if (state.active !== undefined) - data.forEach(function(series,i) { - series.disabled = !state.active[i]; - }); - } - }; - - function chart(selection) { - selection.each(function(data) { - var container = d3.select(this), - that = this; - nv.utils.initSVG(container); - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight1 = nv.utils.availableHeight(height, container, margin) - height2, - availableHeight2 = height2 - margin2.top - margin2.bottom; - - chart.update = function() { container.transition().duration(transitionDuration).call(chart) }; - chart.container = this; - - state - .setter(stateSetter(data), chart.update) - .getter(stateGetter(data)) - .update(); - - // DEPRECATED set state.disableddisabled - state.disabled = data.map(function(d) { return !!d.disabled }); - - if (!defaultState) { - var key; - defaultState = {}; - for (key in state) { - if (state[key] instanceof Array) - defaultState[key] = state[key].slice(0); - else - defaultState[key] = state[key]; - } - } - - // Display No Data message if there's nothing to show. - if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { - nv.utils.noData(chart, container) - return chart; - } else { - container.selectAll('.nv-noData').remove(); - } - - // Setup Scales - x = lines.xScale(); - y = lines.yScale(); - x2 = lines2.xScale(); - y2 = lines2.yScale(); - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-lineWithFocusChart').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineWithFocusChart').append('g'); - var g = wrap.select('g'); - - gEnter.append('g').attr('class', 'nv-legendWrap'); - - var focusEnter = gEnter.append('g').attr('class', 'nv-focus'); - focusEnter.append('g').attr('class', 'nv-x nv-axis'); - focusEnter.append('g').attr('class', 'nv-y nv-axis'); - focusEnter.append('g').attr('class', 'nv-linesWrap'); - focusEnter.append('g').attr('class', 'nv-interactive'); - - var contextEnter = gEnter.append('g').attr('class', 'nv-context'); - contextEnter.append('g').attr('class', 'nv-x nv-axis'); - contextEnter.append('g').attr('class', 'nv-y nv-axis'); - contextEnter.append('g').attr('class', 'nv-linesWrap'); - contextEnter.append('g').attr('class', 'nv-brushBackground'); - contextEnter.append('g').attr('class', 'nv-x nv-brush'); - - // Legend - if (showLegend) { - legend.width(availableWidth); - - g.select('.nv-legendWrap') - .datum(data) - .call(legend); - - if ( margin.top != legend.height()) { - margin.top = legend.height(); - availableHeight1 = nv.utils.availableHeight(height, container, margin) - height2; - } - - g.select('.nv-legendWrap') - .attr('transform', 'translate(0,' + (-margin.top) +')') - } - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - - //Set up interactive layer - if (useInteractiveGuideline) { - interactiveLayer - .width(availableWidth) - .height(availableHeight1) - .margin({left:margin.left, top:margin.top}) - .svgContainer(container) - .xScale(x); - wrap.select(".nv-interactive").call(interactiveLayer); - } - - // Main Chart Component(s) - lines - .width(availableWidth) - .height(availableHeight1) - .color( - data - .map(function(d,i) { - return d.color || color(d, i); - }) - .filter(function(d,i) { - return !data[i].disabled; - }) - ); - - lines2 - .defined(lines.defined()) - .width(availableWidth) - .height(availableHeight2) - .color( - data - .map(function(d,i) { - return d.color || color(d, i); - }) - .filter(function(d,i) { - return !data[i].disabled; - }) - ); - - g.select('.nv-context') - .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')') - - var contextLinesWrap = g.select('.nv-context .nv-linesWrap') - .datum(data.filter(function(d) { return !d.disabled })) - - d3.transition(contextLinesWrap).call(lines2); - - // Setup Main (Focus) Axes - xAxis - .scale(x) - ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) - .tickSize(-availableHeight1, 0); - - yAxis - .scale(y) - ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) ) - .tickSize( -availableWidth, 0); - - g.select('.nv-focus .nv-x.nv-axis') - .attr('transform', 'translate(0,' + availableHeight1 + ')'); - - // Setup Brush - brush - .x(x2) - .on('brush', function() { - onBrush(); - }); - - if (brushExtent) brush.extent(brushExtent); - - var brushBG = g.select('.nv-brushBackground').selectAll('g') - .data([brushExtent || brush.extent()]) - - var brushBGenter = brushBG.enter() - .append('g'); - - brushBGenter.append('rect') - .attr('class', 'left') - .attr('x', 0) - .attr('y', 0) - .attr('height', availableHeight2); - - brushBGenter.append('rect') - .attr('class', 'right') - .attr('x', 0) - .attr('y', 0) - .attr('height', availableHeight2); - - var gBrush = g.select('.nv-x.nv-brush') - .call(brush); - gBrush.selectAll('rect') - .attr('height', availableHeight2); - gBrush.selectAll('.resize').append('path').attr('d', resizePath); - - onBrush(); - - // Setup Secondary (Context) Axes - x2Axis - .scale(x2) - ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) - .tickSize(-availableHeight2, 0); - - g.select('.nv-context .nv-x.nv-axis') - .attr('transform', 'translate(0,' + y2.range()[0] + ')'); - d3.transition(g.select('.nv-context .nv-x.nv-axis')) - .call(x2Axis); - - y2Axis - .scale(y2) - ._ticks( nv.utils.calcTicksY(availableHeight2/36, data) ) - .tickSize( -availableWidth, 0); - - d3.transition(g.select('.nv-context .nv-y.nv-axis')) - .call(y2Axis); - - g.select('.nv-context .nv-x.nv-axis') - .attr('transform', 'translate(0,' + y2.range()[0] + ')'); - - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ - - legend.dispatch.on('stateChange', function(newState) { - for (var key in newState) - state[key] = newState[key]; - dispatch.stateChange(state); - chart.update(); - }); - - interactiveLayer.dispatch.on('elementMousemove', function(e) { - lines.clearHighlights(); - var singlePoint, pointIndex, pointXLocation, allData = []; - data - .filter(function(series, i) { - series.seriesIndex = i; - return !series.disabled; - }) - .forEach(function(series,i) { - var extent = brush.empty() ? x2.domain() : brush.extent(); - var currentValues = series.values.filter(function(d,i) { - return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1]; - }); - - pointIndex = nv.interactiveBisect(currentValues, e.pointXValue, lines.x()); - var point = currentValues[pointIndex]; - var pointYValue = chart.y()(point, pointIndex); - if (pointYValue != null) { - lines.highlightPoint(i, pointIndex, true); - } - if (point === undefined) return; - if (singlePoint === undefined) singlePoint = point; - if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex)); - allData.push({ - key: series.key, - value: chart.y()(point, pointIndex), - color: color(series,series.seriesIndex) - }); - }); - //Highlight the tooltip entry based on which point the mouse is closest to. - if (allData.length > 2) { - var yValue = chart.yScale().invert(e.mouseY); - var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]); - var threshold = 0.03 * domainExtent; - var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold); - if (indexToHighlight !== null) - allData[indexToHighlight].highlight = true; - } - - var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex)); - interactiveLayer.tooltip - .position({left: e.mouseX + margin.left, top: e.mouseY + margin.top}) - .chartContainer(that.parentNode) - .valueFormatter(function(d,i) { - return d == null ? "N/A" : yAxis.tickFormat()(d); - }) - .data({ - value: xValue, - index: pointIndex, - series: allData - })(); - - interactiveLayer.renderGuideLine(pointXLocation); - - }); - - interactiveLayer.dispatch.on("elementMouseout",function(e) { - lines.clearHighlights(); - }); - - dispatch.on('changeState', function(e) { - if (typeof e.disabled !== 'undefined') { - data.forEach(function(series,i) { - series.disabled = e.disabled[i]; - }); - } - chart.update(); - }); - - //============================================================ - // Functions - //------------------------------------------------------------ - - // Taken from crossfilter (http://square.github.com/crossfilter/) - function resizePath(d) { - var e = +(d == 'e'), - x = e ? 1 : -1, - y = availableHeight2 / 3; - return 'M' + (.5 * x) + ',' + y - + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6) - + 'V' + (2 * y - 6) - + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y) - + 'Z' - + 'M' + (2.5 * x) + ',' + (y + 8) - + 'V' + (2 * y - 8) - + 'M' + (4.5 * x) + ',' + (y + 8) - + 'V' + (2 * y - 8); - } - - - function updateBrushBG() { - if (!brush.empty()) brush.extent(brushExtent); - brushBG - .data([brush.empty() ? x2.domain() : brushExtent]) - .each(function(d,i) { - var leftWidth = x2(d[0]) - x.range()[0], - rightWidth = availableWidth - x2(d[1]); - d3.select(this).select('.left') - .attr('width', leftWidth < 0 ? 0 : leftWidth); - - d3.select(this).select('.right') - .attr('x', x2(d[1])) - .attr('width', rightWidth < 0 ? 0 : rightWidth); - }); - } - - - function onBrush() { - brushExtent = brush.empty() ? null : brush.extent(); - var extent = brush.empty() ? x2.domain() : brush.extent(); - - //The brush extent cannot be less than one. If it is, don't update the line chart. - if (Math.abs(extent[0] - extent[1]) <= 1) { - return; - } - - dispatch.brush({extent: extent, brush: brush}); - - - updateBrushBG(); - - // Update Main (Focus) - var focusLinesWrap = g.select('.nv-focus .nv-linesWrap') - .datum( - data - .filter(function(d) { return !d.disabled }) - .map(function(d,i) { - return { - key: d.key, - area: d.area, - values: d.values.filter(function(d,i) { - return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1]; - }) - } - }) - ); - focusLinesWrap.transition().duration(transitionDuration).call(lines); - - - // Update Main (Focus) Axes - g.select('.nv-focus .nv-x.nv-axis').transition().duration(transitionDuration) - .call(xAxis); - g.select('.nv-focus .nv-y.nv-axis').transition().duration(transitionDuration) - .call(yAxis); - } - }); - - return chart; - } - - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ - - lines.dispatch.on('elementMouseover.tooltip', function(evt) { - tooltip.data(evt).position(evt.pos).hidden(false); - }); - - lines.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true) - }); - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - // expose chart's sub-components - chart.dispatch = dispatch; - chart.legend = legend; - chart.lines = lines; - chart.lines2 = lines2; - chart.xAxis = xAxis; - chart.yAxis = yAxis; - chart.x2Axis = x2Axis; - chart.y2Axis = y2Axis; - chart.interactiveLayer = interactiveLayer; - chart.tooltip = tooltip; - - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - focusHeight: {get: function(){return height2;}, set: function(_){height2=_;}}, - showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, - brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}}, - defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, - noData: {get: function(){return noData;}, set: function(_){noData=_;}}, - - // deprecated options - tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); - tooltip.enabled(!!_); - }}, - tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); - tooltip.contentGenerator(_); - }}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - focusMargin: {get: function(){return margin2;}, set: function(_){ - margin2.top = _.top !== undefined ? _.top : margin2.top; - margin2.right = _.right !== undefined ? _.right : margin2.right; - margin2.bottom = _.bottom !== undefined ? _.bottom : margin2.bottom; - margin2.left = _.left !== undefined ? _.left : margin2.left; - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - legend.color(color); - // line color is handled above? - }}, - interpolate: {get: function(){return lines.interpolate();}, set: function(_){ - lines.interpolate(_); - lines2.interpolate(_); - }}, - xTickFormat: {get: function(){return xAxis.tickFormat();}, set: function(_){ - xAxis.tickFormat(_); - x2Axis.tickFormat(_); - }}, - yTickFormat: {get: function(){return yAxis.tickFormat();}, set: function(_){ - yAxis.tickFormat(_); - y2Axis.tickFormat(_); - }}, - duration: {get: function(){return transitionDuration;}, set: function(_){ - transitionDuration=_; - yAxis.duration(transitionDuration); - y2Axis.duration(transitionDuration); - xAxis.duration(transitionDuration); - x2Axis.duration(transitionDuration); - }}, - x: {get: function(){return lines.x();}, set: function(_){ - lines.x(_); - lines2.x(_); - }}, - y: {get: function(){return lines.y();}, set: function(_){ - lines.y(_); - lines2.y(_); - }}, - useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){ - useInteractiveGuideline = _; - if (useInteractiveGuideline) { - lines.interactive(false); - lines.useVoronoi(false); - } - }} - }); - - nv.utils.inheritOptions(chart, lines); - nv.utils.initOptions(chart); - - return chart; - }; - nv.models.multiBar = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = 960 - , height = 500 - , x = d3.scale.ordinal() - , y = d3.scale.linear() - , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one - , container = null - , getX = function(d) { return d.x } - , getY = function(d) { return d.y } - , getYerr = function(d) { return d.yErr } - , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove - , clipEdge = true - , stacked = false - , stackOffset = 'zero' // options include 'silhouette', 'wiggle', 'expand', 'zero', or a custom function - , color = nv.utils.defaultColor() - , errorBarColor = nv.utils.defaultColor() - , hideable = false - , barColor = null // adding the ability to set the color for each rather than the whole group - , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled - , duration = 500 - , xDomain - , yDomain - , xRange - , yRange - , groupSpacing = 0.1 - , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') - ; - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - var x0, y0 //used to store previous scales - , renderWatch = nv.utils.renderWatch(dispatch, duration) - ; - - var last_datalength = 0; - - function chart(selection) { - renderWatch.reset(); - selection.each(function(data) { - var availableWidth = width - margin.left - margin.right, - availableHeight = height - margin.top - margin.bottom; - - container = d3.select(this); - nv.utils.initSVG(container); - var nonStackableCount = 0; - // This function defines the requirements for render complete - var endFn = function(d, i) { - if (d.series === data.length - 1 && i === data[0].values.length - 1) - return true; - return false; - }; - - if(hideable && data.length) hideable = [{ - values: data[0].values.map(function(d) { - return { - x: d.x, - y: 0, - series: d.series, - size: 0.01 - };} - )}]; - - if (stacked) { - var parsed = d3.layout.stack() - .offset(stackOffset) - .values(function(d){ return d.values }) - .y(getY) - (!data.length && hideable ? hideable : data); - - parsed.forEach(function(series, i){ - // if series is non-stackable, use un-parsed data - if (series.nonStackable) { - data[i].nonStackableSeries = nonStackableCount++; - parsed[i] = data[i]; - } else { - // don't stack this seires on top of the nonStackable seriees - if (i > 0 && parsed[i - 1].nonStackable){ - parsed[i].values.map(function(d,j){ - d.y0 -= parsed[i - 1].values[j].y; - d.y1 = d.y0 + d.y; - }); - } - } - }); - data = parsed; - } - //add series index and key to each data point for reference - data.forEach(function(series, i) { - series.values.forEach(function(point) { - point.series = i; - point.key = series.key; - }); - }); - - // HACK for negative value stacking - if (stacked) { - data[0].values.map(function(d,i) { - var posBase = 0, negBase = 0; - data.map(function(d, idx) { - if (!data[idx].nonStackable) { - var f = d.values[i] - f.size = Math.abs(f.y); - if (f.y<0) { - f.y1 = negBase; - negBase = negBase - f.size; - } else - { - f.y1 = f.size + posBase; - posBase = posBase + f.size; - } - } - - }); - }); - } - // Setup Scales - // remap and flatten the data for use in calculating the scales' domains - var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate - data.map(function(d, idx) { - return d.values.map(function(d,i) { - return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1, idx:idx, yErr: getYerr(d,i)} - }) - }); - - x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x })) - .rangeBands(xRange || [0, availableWidth], groupSpacing); - - y.domain(yDomain || d3.extent(d3.merge( - d3.merge(seriesData).map(function(d) { - var domain = d.y; - // increase the domain range if this series is stackable - if (stacked && !data[d.idx].nonStackable) { - if (d.y > 0){ - domain = d.y1 - } else { - domain = d.y1 + d.y - } - } - var yerr = d.yErr; - if (yerr) { - if (yerr.length) { - return [domain + yerr[0], domain + yerr[1]]; - } else { - yerr = Math.abs(yerr) - return [domain - yerr, domain + yerr]; - } - } else { - return [domain]; - } - })).concat(forceY))) - .range(yRange || [availableHeight, 0]); - - // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point - if (x.domain()[0] === x.domain()[1]) - x.domain()[0] ? - x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) - : x.domain([-1,1]); - - if (y.domain()[0] === y.domain()[1]) - y.domain()[0] ? - y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) - : y.domain([-1,1]); - - x0 = x0 || x; - y0 = y0 || y; - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar'); - var defsEnter = wrapEnter.append('defs'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); - - gEnter.append('g').attr('class', 'nv-groups'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - defsEnter.append('clipPath') - .attr('id', 'nv-edge-clip-' + id) - .append('rect'); - wrap.select('#nv-edge-clip-' + id + ' rect') - .attr('width', availableWidth) - .attr('height', availableHeight); - - g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); - - var groups = wrap.select('.nv-groups').selectAll('.nv-group') - .data(function(d) { return d }, function(d,i) { return i }); - groups.enter().append('g') - .style('stroke-opacity', 1e-6) - .style('fill-opacity', 1e-6); - - var exitTransition = renderWatch - .transition(groups.exit().selectAll('g.nv-bar'), 'multibarExit', Math.min(100, duration)) - .attr('y', function(d, i, j) { - var yVal = y0(0) || 0; - if (stacked) { - if (data[d.series] && !data[d.series].nonStackable) { - yVal = y0(d.y0); - } - } - return yVal; - }) - .attr('height', 0) - .remove(); - if (exitTransition.delay) - exitTransition.delay(function(d,i) { - var delay = i * (duration / (last_datalength + 1)) - i; - return delay; - }); - groups - .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) - .classed('hover', function(d) { return d.hover }) - .style('fill', function(d,i){ return color(d, i) }) - .style('stroke', function(d,i){ return color(d, i) }); - groups - .style('stroke-opacity', 1) - .style('fill-opacity', 0.75); - - var bars = groups.selectAll('g.nv-bar') - .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values }); - bars.exit().remove(); - - var barsEnter = bars.enter().append('g') - .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) - .attr('transform', function(d,i,j) { - var _x = stacked && !data[j].nonStackable ? 0 : (j * x.rangeBand() / data.length ); - var _y = y0(stacked && !data[j].nonStackable ? d.y0 : 0) || 0; - return 'translate(' + _x + ',' + _y + ')'; - }) - ; - - barsEnter.append('rect') - .attr('height', 0) - .attr('width', function(d,i,j) { return x.rangeBand() / (stacked && !data[j].nonStackable ? 1 : data.length) }) - .style('fill', function(d,i,j){ return color(d, j, i); }) - .style('stroke', function(d,i,j){ return color(d, j, i); }) - ; - bars - .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here - d3.select(this).classed('hover', true); - dispatch.elementMouseover({ - data: d, - index: i, - color: d3.select(this).style("fill") - }); - }) - .on('mouseout', function(d,i) { - d3.select(this).classed('hover', false); - dispatch.elementMouseout({ - data: d, - index: i, - color: d3.select(this).style("fill") - }); - }) - .on('mousemove', function(d,i) { - dispatch.elementMousemove({ - data: d, - index: i, - color: d3.select(this).style("fill") - }); - }) - .on('click', function(d,i) { - dispatch.elementClick({ - data: d, - index: i, - color: d3.select(this).style("fill") - }); - d3.event.stopPropagation(); - }) - .on('dblclick', function(d,i) { - dispatch.elementDblClick({ - data: d, - index: i, - color: d3.select(this).style("fill") - }); - d3.event.stopPropagation(); - }); - - if (getYerr(data[0].values[0], 0)) { - barsEnter.append('polyline'); - - bars.select('polyline') - .attr('fill', 'none') - .attr('stroke', function(d, i, j) { return errorBarColor(d, j, i); }) - .attr('points', function(d,i) { - var yerr = getYerr(d,i) - , mid = 0.8 * x.rangeBand() / ((stacked ? 1 : data.length) * 2); - yerr = yerr.length ? yerr : [-Math.abs(yerr), Math.abs(yerr)]; - yerr = yerr.map(function(e) { return y(e) - y(0); }); - var a = [[-mid, yerr[0]], [mid, yerr[0]], [0, yerr[0]], [0, yerr[1]], [-mid, yerr[1]], [mid, yerr[1]]]; - return a.map(function (path) { return path.join(',') }).join(' '); - }) - .attr('transform', function(d, i) { - var xOffset = x.rangeBand() / ((stacked ? 1 : data.length) * 2); - var yOffset = getY(d,i) < 0 ? y(getY(d, i)) - y(0) : 0; - return 'translate(' + xOffset + ', ' + yOffset + ')'; - }) - } - - bars - .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) - - if (barColor) { - if (!disabled) disabled = data.map(function() { return true }); - bars.select('rect') - .style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); }) - .style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); }); - } - - var barSelection = - bars.watchTransition(renderWatch, 'multibar', Math.min(250, duration)) - .delay(function(d,i) { - return i * duration / data[0].values.length; - }); - if (stacked){ - barSelection - .attr('transform', function(d,i,j) { - var yVal = 0; - // if stackable, stack it on top of the previous series - if (!data[j].nonStackable) { - yVal = y(d.y1); - } else { - if (getY(d,i) < 0){ - yVal = y(0); - } else { - if (y(0) - y(getY(d,i)) < -1){ - yVal = y(0) - 1; - } else { - yVal = y(getY(d, i)) || 0; - } - } - } - var width = 0; - if (data[j].nonStackable) { - width = d.series * x.rangeBand() / data.length; - if (data.length !== nonStackableCount){ - width = data[j].nonStackableSeries * x.rangeBand()/(nonStackableCount*2); - } - } - var xVal = width + x(getX(d, i)); - return 'translate(' + xVal + ',' + yVal + ')'; - }) - .select('rect') - .attr('height', function(d,i,j) { - if (!data[j].nonStackable) { - return Math.max(Math.abs(y(d.y+d.y0) - y(d.y0)), 1); - } else { - return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0; - } - }) - .attr('width', function(d,i,j){ - if (!data[j].nonStackable) { - return x.rangeBand(); - } else { - // if all series are nonStacable, take the full width - var width = (x.rangeBand() / nonStackableCount); - // otherwise, nonStackable graph will be only taking the half-width - // of the x rangeBand - if (data.length !== nonStackableCount) { - width = x.rangeBand()/(nonStackableCount*2); - } - return width; - } - }); - } - else { - barSelection.attr('transform', function(d,i) { - var xVal = d.series * x.rangeBand() / data.length + x(getX(d, i)); - var yVal = getY(d,i) < 0 ? - y(0) : - y(0) - y(getY(d,i)) < 1 ? - y(0) - 1 : - y(getY(d,i)) || 0; - return 'translate(' + xVal + ',' + yVal + ')'; - }) - .select('rect') - .attr('width', x.rangeBand() / data.length) - .attr('height', function(d,i) { - return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0; - }); - } - - //store old scales for use in transitions on update - x0 = x.copy(); - y0 = y.copy(); - - // keep track of the last data value length for transition calculations - if (data[0] && data[0].values) { - last_datalength = data[0].values.length; - } - - }); - - renderWatch.renderEnd('multibar immediate'); - - return chart; - } - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - chart.dispatch = dispatch; - - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - x: {get: function(){return getX;}, set: function(_){getX=_;}}, - y: {get: function(){return getY;}, set: function(_){getY=_;}}, - yErr: {get: function(){return getYerr;}, set: function(_){getYerr=_;}}, - xScale: {get: function(){return x;}, set: function(_){x=_;}}, - yScale: {get: function(){return y;}, set: function(_){y=_;}}, - xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, - yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, - xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, - yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, - forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, - stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}}, - stackOffset: {get: function(){return stackOffset;}, set: function(_){stackOffset=_;}}, - clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, - disabled: {get: function(){return disabled;}, set: function(_){disabled=_;}}, - id: {get: function(){return id;}, set: function(_){id=_;}}, - hideable: {get: function(){return hideable;}, set: function(_){hideable=_;}}, - groupSpacing:{get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - duration: {get: function(){return duration;}, set: function(_){ - duration = _; - renderWatch.reset(duration); - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - }}, - barColor: {get: function(){return barColor;}, set: function(_){ - barColor = _ ? nv.utils.getColor(_) : null; - }}, - errorBarColor: {get: function(){return errorBarColor;}, set: function(_){ - errorBarColor = _ ? nv.utils.getColor(_) : null; - }} - }); - - nv.utils.initOptions(chart); - - return chart; - }; - - nv.models.multiBarChart = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var multibar = nv.models.multiBar() - , xAxis = nv.models.axis() - , yAxis = nv.models.axis() - , legend = nv.models.legend() - , controls = nv.models.legend() - , tooltip = nv.models.tooltip() - ; - - var margin = {top: 30, right: 20, bottom: 50, left: 60} - , width = null - , height = null - , color = nv.utils.defaultColor() - , showControls = true - , controlLabels = {} - , showLegend = true - , showXAxis = true - , showYAxis = true - , rightAlignYAxis = false - , reduceXTicks = true // if false a tick will show for every data point - , staggerLabels = false - , rotateLabels = 0 - , x //can be accessed via chart.xScale() - , y //can be accessed via chart.yScale() - , state = nv.utils.state() - , defaultState = null - , noData = null - , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd') - , controlWidth = function() { return showControls ? 180 : 0 } - , duration = 250 - ; - - state.stacked = false // DEPRECATED Maintained for backward compatibility - - multibar.stacked(false); - xAxis - .orient('bottom') - .tickPadding(7) - .showMaxMin(false) - .tickFormat(function(d) { return d }) - ; - yAxis - .orient((rightAlignYAxis) ? 'right' : 'left') - .tickFormat(d3.format(',.1f')) - ; - - tooltip - .duration(0) - .valueFormatter(function(d, i) { - return yAxis.tickFormat()(d, i); - }) - .headerFormatter(function(d, i) { - return xAxis.tickFormat()(d, i); - }); - - controls.updateState(false); - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - var renderWatch = nv.utils.renderWatch(dispatch); - var stacked = false; - - var stateGetter = function(data) { - return function(){ - return { - active: data.map(function(d) { return !d.disabled }), - stacked: stacked - }; - } - }; - - var stateSetter = function(data) { - return function(state) { - if (state.stacked !== undefined) - stacked = state.stacked; - if (state.active !== undefined) - data.forEach(function(series,i) { - series.disabled = !state.active[i]; - }); - } - }; - - function chart(selection) { - renderWatch.reset(); - renderWatch.models(multibar); - if (showXAxis) renderWatch.models(xAxis); - if (showYAxis) renderWatch.models(yAxis); - - selection.each(function(data) { - var container = d3.select(this), - that = this; - nv.utils.initSVG(container); - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); - - chart.update = function() { - if (duration === 0) - container.call(chart); - else - container.transition() - .duration(duration) - .call(chart); - }; - chart.container = this; - - state - .setter(stateSetter(data), chart.update) - .getter(stateGetter(data)) - .update(); - - // DEPRECATED set state.disableddisabled - state.disabled = data.map(function(d) { return !!d.disabled }); - - if (!defaultState) { - var key; - defaultState = {}; - for (key in state) { - if (state[key] instanceof Array) - defaultState[key] = state[key].slice(0); - else - defaultState[key] = state[key]; - } - } - - // Display noData message if there's nothing to show. - if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { - nv.utils.noData(chart, container) - return chart; - } else { - container.selectAll('.nv-noData').remove(); - } - - // Setup Scales - x = multibar.xScale(); - y = multibar.yScale(); - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g'); - var g = wrap.select('g'); - - gEnter.append('g').attr('class', 'nv-x nv-axis'); - gEnter.append('g').attr('class', 'nv-y nv-axis'); - gEnter.append('g').attr('class', 'nv-barsWrap'); - gEnter.append('g').attr('class', 'nv-legendWrap'); - gEnter.append('g').attr('class', 'nv-controlsWrap'); - - // Legend - if (showLegend) { - legend.width(availableWidth - controlWidth()); - - g.select('.nv-legendWrap') - .datum(data) - .call(legend); - - if ( margin.top != legend.height()) { - margin.top = legend.height(); - availableHeight = nv.utils.availableHeight(height, container, margin); - } - - g.select('.nv-legendWrap') - .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')'); - } - - // Controls - if (showControls) { - var controlsData = [ - { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() }, - { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() } - ]; - - controls.width(controlWidth()).color(['#444', '#444', '#444']); - g.select('.nv-controlsWrap') - .datum(controlsData) - .attr('transform', 'translate(0,' + (-margin.top) +')') - .call(controls); - } - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - if (rightAlignYAxis) { - g.select(".nv-y.nv-axis") - .attr("transform", "translate(" + availableWidth + ",0)"); - } - - // Main Chart Component(s) - multibar - .disabled(data.map(function(series) { return series.disabled })) - .width(availableWidth) - .height(availableHeight) - .color(data.map(function(d,i) { - return d.color || color(d, i); - }).filter(function(d,i) { return !data[i].disabled })); - - - var barsWrap = g.select('.nv-barsWrap') - .datum(data.filter(function(d) { return !d.disabled })); - - barsWrap.call(multibar); - - // Setup Axes - if (showXAxis) { - xAxis - .scale(x) - ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) - .tickSize(-availableHeight, 0); - - g.select('.nv-x.nv-axis') - .attr('transform', 'translate(0,' + y.range()[0] + ')'); - g.select('.nv-x.nv-axis') - .call(xAxis); - - var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g'); - - xTicks - .selectAll('line, text') - .style('opacity', 1) - - if (staggerLabels) { - var getTranslate = function(x,y) { - return "translate(" + x + "," + y + ")"; - }; - - var staggerUp = 5, staggerDown = 17; //pixels to stagger by - // Issue #140 - xTicks - .selectAll("text") - .attr('transform', function(d,i,j) { - return getTranslate(0, (j % 2 == 0 ? staggerUp : staggerDown)); - }); - - var totalInBetweenTicks = d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length; - g.selectAll(".nv-x.nv-axis .nv-axisMaxMin text") - .attr("transform", function(d,i) { - return getTranslate(0, (i === 0 || totalInBetweenTicks % 2 !== 0) ? staggerDown : staggerUp); - }); - } - - if (reduceXTicks) - xTicks - .filter(function(d,i) { - return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0; - }) - .selectAll('text, line') - .style('opacity', 0); - - if(rotateLabels) - xTicks - .selectAll('.tick text') - .attr('transform', 'rotate(' + rotateLabels + ' 0,0)') - .style('text-anchor', rotateLabels > 0 ? 'start' : 'end'); - - g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text') - .style('opacity', 1); - } - - if (showYAxis) { - yAxis - .scale(y) - ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) - .tickSize( -availableWidth, 0); - - g.select('.nv-y.nv-axis') - .call(yAxis); - } - - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ - - legend.dispatch.on('stateChange', function(newState) { - for (var key in newState) - state[key] = newState[key]; - dispatch.stateChange(state); - chart.update(); - }); - - controls.dispatch.on('legendClick', function(d,i) { - if (!d.disabled) return; - controlsData = controlsData.map(function(s) { - s.disabled = true; - return s; - }); - d.disabled = false; - - switch (d.key) { - case 'Grouped': - case controlLabels.grouped: - multibar.stacked(false); - break; - case 'Stacked': - case controlLabels.stacked: - multibar.stacked(true); - break; - } - - state.stacked = multibar.stacked(); - dispatch.stateChange(state); - chart.update(); - }); - - // Update chart from a state object passed to event handler - dispatch.on('changeState', function(e) { - if (typeof e.disabled !== 'undefined') { - data.forEach(function(series,i) { - series.disabled = e.disabled[i]; - }); - state.disabled = e.disabled; - } - if (typeof e.stacked !== 'undefined') { - multibar.stacked(e.stacked); - state.stacked = e.stacked; - stacked = e.stacked; - } - chart.update(); - }); - }); - - renderWatch.renderEnd('multibarchart immediate'); - return chart; - } - - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ - - multibar.dispatch.on('elementMouseover.tooltip', function(evt) { - evt.value = chart.x()(evt.data); - evt['series'] = { - key: evt.data.key, - value: chart.y()(evt.data), - color: evt.color - }; - tooltip.data(evt).hidden(false); - }); - - multibar.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true); - }); - - multibar.dispatch.on('elementMousemove.tooltip', function(evt) { - tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); - }); - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - // expose chart's sub-components - chart.dispatch = dispatch; - chart.multibar = multibar; - chart.legend = legend; - chart.controls = controls; - chart.xAxis = xAxis; - chart.yAxis = yAxis; - chart.state = state; - chart.tooltip = tooltip; - - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, - showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}}, - controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}}, - showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, - showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, - defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, - noData: {get: function(){return noData;}, set: function(_){noData=_;}}, - reduceXTicks: {get: function(){return reduceXTicks;}, set: function(_){reduceXTicks=_;}}, - rotateLabels: {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}}, - staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}}, - - // deprecated options - tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); - tooltip.enabled(!!_); - }}, - tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); - tooltip.contentGenerator(_); - }}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - duration: {get: function(){return duration;}, set: function(_){ - duration = _; - multibar.duration(duration); - xAxis.duration(duration); - yAxis.duration(duration); - renderWatch.reset(duration); - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - legend.color(color); - }}, - rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ - rightAlignYAxis = _; - yAxis.orient( rightAlignYAxis ? 'right' : 'left'); - }}, - barColor: {get: function(){return multibar.barColor;}, set: function(_){ - multibar.barColor(_); - legend.color(function(d,i) {return d3.rgb('#ccc').darker(i * 1.5).toString();}) - }} - }); - - nv.utils.inheritOptions(chart, multibar); - nv.utils.initOptions(chart); - - return chart; - }; - - nv.models.multiBarHorizontal = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = 960 - , height = 500 - , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one - , container = null - , x = d3.scale.ordinal() - , y = d3.scale.linear() - , getX = function(d) { return d.x } - , getY = function(d) { return d.y } - , getYerr = function(d) { return d.yErr } - , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove - , color = nv.utils.defaultColor() - , barColor = null // adding the ability to set the color for each rather than the whole group - , errorBarColor = nv.utils.defaultColor() - , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled - , stacked = false - , showValues = false - , showBarLabels = false - , valuePadding = 60 - , groupSpacing = 0.1 - , valueFormat = d3.format(',.2f') - , delay = 1200 - , xDomain - , yDomain - , xRange - , yRange - , duration = 250 - , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') - ; - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - var x0, y0; //used to store previous scales - var renderWatch = nv.utils.renderWatch(dispatch, duration); - - function chart(selection) { - renderWatch.reset(); - selection.each(function(data) { - var availableWidth = width - margin.left - margin.right, - availableHeight = height - margin.top - margin.bottom; - - container = d3.select(this); - nv.utils.initSVG(container); - - if (stacked) - data = d3.layout.stack() - .offset('zero') - .values(function(d){ return d.values }) - .y(getY) - (data); - - //add series index and key to each data point for reference - data.forEach(function(series, i) { - series.values.forEach(function(point) { - point.series = i; - point.key = series.key; - }); - }); - - // HACK for negative value stacking - if (stacked) - data[0].values.map(function(d,i) { - var posBase = 0, negBase = 0; - data.map(function(d) { - var f = d.values[i] - f.size = Math.abs(f.y); - if (f.y<0) { - f.y1 = negBase - f.size; - negBase = negBase - f.size; - } else - { - f.y1 = posBase; - posBase = posBase + f.size; - } - }); - }); - - // Setup Scales - // remap and flatten the data for use in calculating the scales' domains - var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate - data.map(function(d) { - return d.values.map(function(d,i) { - return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1, yErr: getYerr(d,i) } - }) - }); - - x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x })) - .rangeBands(xRange || [0, availableHeight], groupSpacing); - - y.domain(yDomain || d3.extent(d3.merge( - d3.merge(seriesData).map(function(d) { - var domain = d.y; - if (stacked) { - if (d.y > 0){ - domain = d.y1 + d.y - } else { - domain = d.y1 - } - } - var yerr = d.yErr; - if (yerr) { - if (yerr.length) { - return [domain + yerr[0], domain + yerr[1]]; - } else { - yerr = Math.abs(yerr) - return [domain - yerr, domain + yerr]; - } - } else { - return [domain]; - } - })).concat(forceY))) - - if (showValues && !stacked) - y.range(yRange || [(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]); - else - y.range(yRange || [0, availableWidth]); - - x0 = x0 || x; - y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]); - - // Setup containers and skeleton of chart - var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal'); - var defsEnter = wrapEnter.append('defs'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); - - gEnter.append('g').attr('class', 'nv-groups'); - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - var groups = wrap.select('.nv-groups').selectAll('.nv-group') - .data(function(d) { return d }, function(d,i) { return i }); - groups.enter().append('g') - .style('stroke-opacity', 1e-6) - .style('fill-opacity', 1e-6); - groups.exit().watchTransition(renderWatch, 'multibarhorizontal: exit groups') - .style('stroke-opacity', 1e-6) - .style('fill-opacity', 1e-6) - .remove(); - groups - .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) - .classed('hover', function(d) { return d.hover }) - .style('fill', function(d,i){ return color(d, i) }) - .style('stroke', function(d,i){ return color(d, i) }); - groups.watchTransition(renderWatch, 'multibarhorizontal: groups') - .style('stroke-opacity', 1) - .style('fill-opacity', .75); - - var bars = groups.selectAll('g.nv-bar') - .data(function(d) { return d.values }); - bars.exit().remove(); - - var barsEnter = bars.enter().append('g') - .attr('transform', function(d,i,j) { - return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')' - }); - - barsEnter.append('rect') - .attr('width', 0) - .attr('height', x.rangeBand() / (stacked ? 1 : data.length) ) - - bars - .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here - d3.select(this).classed('hover', true); - dispatch.elementMouseover({ - data: d, - index: i, - color: d3.select(this).style("fill") - }); - }) - .on('mouseout', function(d,i) { - d3.select(this).classed('hover', false); - dispatch.elementMouseout({ - data: d, - index: i, - color: d3.select(this).style("fill") - }); - }) - .on('mouseout', function(d,i) { - dispatch.elementMouseout({ - data: d, - index: i, - color: d3.select(this).style("fill") - }); - }) - .on('mousemove', function(d,i) { - dispatch.elementMousemove({ - data: d, - index: i, - color: d3.select(this).style("fill") - }); - }) - .on('click', function(d,i) { - dispatch.elementClick({ - data: d, - index: i, - color: d3.select(this).style("fill") - }); - d3.event.stopPropagation(); - }) - .on('dblclick', function(d,i) { - dispatch.elementDblClick({ - data: d, - index: i, - color: d3.select(this).style("fill") - }); - d3.event.stopPropagation(); - }); - - if (getYerr(data[0].values[0], 0)) { - barsEnter.append('polyline'); - - bars.select('polyline') - .attr('fill', 'none') - .attr('stroke', function(d,i,j) { return errorBarColor(d, j, i); }) - .attr('points', function(d,i) { - var xerr = getYerr(d,i) - , mid = 0.8 * x.rangeBand() / ((stacked ? 1 : data.length) * 2); - xerr = xerr.length ? xerr : [-Math.abs(xerr), Math.abs(xerr)]; - xerr = xerr.map(function(e) { return y(e) - y(0); }); - var a = [[xerr[0],-mid], [xerr[0],mid], [xerr[0],0], [xerr[1],0], [xerr[1],-mid], [xerr[1],mid]]; - return a.map(function (path) { return path.join(',') }).join(' '); - }) - .attr('transform', function(d,i) { - var mid = x.rangeBand() / ((stacked ? 1 : data.length) * 2); - return 'translate(' + (getY(d,i) < 0 ? 0 : y(getY(d,i)) - y(0)) + ', ' + mid + ')' - }); - } - - barsEnter.append('text'); - - if (showValues && !stacked) { - bars.select('text') - .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' }) - .attr('y', x.rangeBand() / (data.length * 2)) - .attr('dy', '.32em') - .text(function(d,i) { - var t = valueFormat(getY(d,i)) - , yerr = getYerr(d,i); - if (yerr === undefined) - return t; - if (!yerr.length) - return t + '±' + valueFormat(Math.abs(yerr)); - return t + '+' + valueFormat(Math.abs(yerr[1])) + '-' + valueFormat(Math.abs(yerr[0])); - }); - bars.watchTransition(renderWatch, 'multibarhorizontal: bars') - .select('text') - .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 }) - } else { - bars.selectAll('text').text(''); - } - - if (showBarLabels && !stacked) { - barsEnter.append('text').classed('nv-bar-label',true); - bars.select('text.nv-bar-label') - .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'start' : 'end' }) - .attr('y', x.rangeBand() / (data.length * 2)) - .attr('dy', '.32em') - .text(function(d,i) { return getX(d,i) }); - bars.watchTransition(renderWatch, 'multibarhorizontal: bars') - .select('text.nv-bar-label') - .attr('x', function(d,i) { return getY(d,i) < 0 ? y(0) - y(getY(d,i)) + 4 : -4 }); - } - else { - bars.selectAll('text.nv-bar-label').text(''); - } - - bars - .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) - - if (barColor) { - if (!disabled) disabled = data.map(function() { return true }); - bars - .style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); }) - .style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); }); - } - - if (stacked) - bars.watchTransition(renderWatch, 'multibarhorizontal: bars') - .attr('transform', function(d,i) { - return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')' - }) - .select('rect') - .attr('width', function(d,i) { - return Math.abs(y(getY(d,i) + d.y0) - y(d.y0)) - }) - .attr('height', x.rangeBand() ); - else - bars.watchTransition(renderWatch, 'multibarhorizontal: bars') - .attr('transform', function(d,i) { - //TODO: stacked must be all positive or all negative, not both? - return 'translate(' + - (getY(d,i) < 0 ? y(getY(d,i)) : y(0)) - + ',' + - (d.series * x.rangeBand() / data.length - + - x(getX(d,i)) ) - + ')' - }) - .select('rect') - .attr('height', x.rangeBand() / data.length ) - .attr('width', function(d,i) { - return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) - }); - - //store old scales for use in transitions on update - x0 = x.copy(); - y0 = y.copy(); - - }); - - renderWatch.renderEnd('multibarHorizontal immediate'); - return chart; - } - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - chart.dispatch = dispatch; - - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - x: {get: function(){return getX;}, set: function(_){getX=_;}}, - y: {get: function(){return getY;}, set: function(_){getY=_;}}, - yErr: {get: function(){return getYerr;}, set: function(_){getYerr=_;}}, - xScale: {get: function(){return x;}, set: function(_){x=_;}}, - yScale: {get: function(){return y;}, set: function(_){y=_;}}, - xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, - yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, - xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, - yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, - forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, - stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}}, - showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}}, - // this shows the group name, seems pointless? - //showBarLabels: {get: function(){return showBarLabels;}, set: function(_){showBarLabels=_;}}, - disabled: {get: function(){return disabled;}, set: function(_){disabled=_;}}, - id: {get: function(){return id;}, set: function(_){id=_;}}, - valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}}, - valuePadding: {get: function(){return valuePadding;}, set: function(_){valuePadding=_;}}, - groupSpacing:{get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - duration: {get: function(){return duration;}, set: function(_){ - duration = _; - renderWatch.reset(duration); - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - }}, - barColor: {get: function(){return barColor;}, set: function(_){ - barColor = _ ? nv.utils.getColor(_) : null; - }}, - errorBarColor: {get: function(){return errorBarColor;}, set: function(_){ - errorBarColor = _ ? nv.utils.getColor(_) : null; - }} - }); - - nv.utils.initOptions(chart); - - return chart; - }; - - nv.models.multiBarHorizontalChart = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var multibar = nv.models.multiBarHorizontal() - , xAxis = nv.models.axis() - , yAxis = nv.models.axis() - , legend = nv.models.legend().height(30) - , controls = nv.models.legend().height(30) - , tooltip = nv.models.tooltip() - ; - - var margin = {top: 30, right: 20, bottom: 50, left: 60} - , width = null - , height = null - , color = nv.utils.defaultColor() - , showControls = true - , controlLabels = {} - , showLegend = true - , showXAxis = true - , showYAxis = true - , stacked = false - , x //can be accessed via chart.xScale() - , y //can be accessed via chart.yScale() - , state = nv.utils.state() - , defaultState = null - , noData = null - , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd') - , controlWidth = function() { return showControls ? 180 : 0 } - , duration = 250 - ; - - state.stacked = false; // DEPRECATED Maintained for backward compatibility - - multibar.stacked(stacked); - - xAxis - .orient('left') - .tickPadding(5) - .showMaxMin(false) - .tickFormat(function(d) { return d }) - ; - yAxis - .orient('bottom') - .tickFormat(d3.format(',.1f')) - ; - - tooltip - .duration(0) - .valueFormatter(function(d, i) { - return yAxis.tickFormat()(d, i); - }) - .headerFormatter(function(d, i) { - return xAxis.tickFormat()(d, i); - }); - - controls.updateState(false); - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - var stateGetter = function(data) { - return function(){ - return { - active: data.map(function(d) { return !d.disabled }), - stacked: stacked - }; - } - }; - - var stateSetter = function(data) { - return function(state) { - if (state.stacked !== undefined) - stacked = state.stacked; - if (state.active !== undefined) - data.forEach(function(series,i) { - series.disabled = !state.active[i]; - }); - } - }; - - var renderWatch = nv.utils.renderWatch(dispatch, duration); - - function chart(selection) { - renderWatch.reset(); - renderWatch.models(multibar); - if (showXAxis) renderWatch.models(xAxis); - if (showYAxis) renderWatch.models(yAxis); - - selection.each(function(data) { - var container = d3.select(this), - that = this; - nv.utils.initSVG(container); - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); - - chart.update = function() { container.transition().duration(duration).call(chart) }; - chart.container = this; - - stacked = multibar.stacked(); - - state - .setter(stateSetter(data), chart.update) - .getter(stateGetter(data)) - .update(); - - // DEPRECATED set state.disableddisabled - state.disabled = data.map(function(d) { return !!d.disabled }); - - if (!defaultState) { - var key; - defaultState = {}; - for (key in state) { - if (state[key] instanceof Array) - defaultState[key] = state[key].slice(0); - else - defaultState[key] = state[key]; - } - } - - // Display No Data message if there's nothing to show. - if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { - nv.utils.noData(chart, container) - return chart; - } else { - container.selectAll('.nv-noData').remove(); - } - - // Setup Scales - x = multibar.xScale(); - y = multibar.yScale(); - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g'); - var g = wrap.select('g'); - - gEnter.append('g').attr('class', 'nv-x nv-axis'); - gEnter.append('g').attr('class', 'nv-y nv-axis') - .append('g').attr('class', 'nv-zeroLine') - .append('line'); - gEnter.append('g').attr('class', 'nv-barsWrap'); - gEnter.append('g').attr('class', 'nv-legendWrap'); - gEnter.append('g').attr('class', 'nv-controlsWrap'); - - // Legend - if (showLegend) { - legend.width(availableWidth - controlWidth()); - - g.select('.nv-legendWrap') - .datum(data) - .call(legend); - - if ( margin.top != legend.height()) { - margin.top = legend.height(); - availableHeight = nv.utils.availableHeight(height, container, margin); - } - - g.select('.nv-legendWrap') - .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')'); - } - - // Controls - if (showControls) { - var controlsData = [ - { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() }, - { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() } - ]; - - controls.width(controlWidth()).color(['#444', '#444', '#444']); - g.select('.nv-controlsWrap') - .datum(controlsData) - .attr('transform', 'translate(0,' + (-margin.top) +')') - .call(controls); - } - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - // Main Chart Component(s) - multibar - .disabled(data.map(function(series) { return series.disabled })) - .width(availableWidth) - .height(availableHeight) - .color(data.map(function(d,i) { - return d.color || color(d, i); - }).filter(function(d,i) { return !data[i].disabled })); - - var barsWrap = g.select('.nv-barsWrap') - .datum(data.filter(function(d) { return !d.disabled })); - - barsWrap.transition().call(multibar); - - // Setup Axes - if (showXAxis) { - xAxis - .scale(x) - ._ticks( nv.utils.calcTicksY(availableHeight/24, data) ) - .tickSize(-availableWidth, 0); - - g.select('.nv-x.nv-axis').call(xAxis); - - var xTicks = g.select('.nv-x.nv-axis').selectAll('g'); - - xTicks - .selectAll('line, text'); - } - - if (showYAxis) { - yAxis - .scale(y) - ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) - .tickSize( -availableHeight, 0); - - g.select('.nv-y.nv-axis') - .attr('transform', 'translate(0,' + availableHeight + ')'); - g.select('.nv-y.nv-axis').call(yAxis); - } - - // Zero line - g.select(".nv-zeroLine line") - .attr("x1", y(0)) - .attr("x2", y(0)) - .attr("y1", 0) - .attr("y2", -availableHeight) - ; - - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ - - legend.dispatch.on('stateChange', function(newState) { - for (var key in newState) - state[key] = newState[key]; - dispatch.stateChange(state); - chart.update(); - }); - - controls.dispatch.on('legendClick', function(d,i) { - if (!d.disabled) return; - controlsData = controlsData.map(function(s) { - s.disabled = true; - return s; - }); - d.disabled = false; - - switch (d.key) { - case 'Grouped': - multibar.stacked(false); - break; - case 'Stacked': - multibar.stacked(true); - break; - } - - state.stacked = multibar.stacked(); - dispatch.stateChange(state); - stacked = multibar.stacked(); - - chart.update(); - }); - - // Update chart from a state object passed to event handler - dispatch.on('changeState', function(e) { - - if (typeof e.disabled !== 'undefined') { - data.forEach(function(series,i) { - series.disabled = e.disabled[i]; - }); - - state.disabled = e.disabled; - } - - if (typeof e.stacked !== 'undefined') { - multibar.stacked(e.stacked); - state.stacked = e.stacked; - stacked = e.stacked; - } - - chart.update(); - }); - }); - renderWatch.renderEnd('multibar horizontal chart immediate'); - return chart; - } - - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ - - multibar.dispatch.on('elementMouseover.tooltip', function(evt) { - evt.value = chart.x()(evt.data); - evt['series'] = { - key: evt.data.key, - value: chart.y()(evt.data), - color: evt.color - }; - tooltip.data(evt).hidden(false); - }); - - multibar.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true); - }); - - multibar.dispatch.on('elementMousemove.tooltip', function(evt) { - tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); - }); - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - // expose chart's sub-components - chart.dispatch = dispatch; - chart.multibar = multibar; - chart.legend = legend; - chart.controls = controls; - chart.xAxis = xAxis; - chart.yAxis = yAxis; - chart.state = state; - chart.tooltip = tooltip; - - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, - showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}}, - controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}}, - showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, - showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, - defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, - noData: {get: function(){return noData;}, set: function(_){noData=_;}}, - - // deprecated options - tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); - tooltip.enabled(!!_); - }}, - tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); - tooltip.contentGenerator(_); - }}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - duration: {get: function(){return duration;}, set: function(_){ - duration = _; - renderWatch.reset(duration); - multibar.duration(duration); - xAxis.duration(duration); - yAxis.duration(duration); - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - legend.color(color); - }}, - barColor: {get: function(){return multibar.barColor;}, set: function(_){ - multibar.barColor(_); - legend.color(function(d,i) {return d3.rgb('#ccc').darker(i * 1.5).toString();}) - }} - }); - - nv.utils.inheritOptions(chart, multibar); - nv.utils.initOptions(chart); - - return chart; - }; - nv.models.multiChart = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var margin = {top: 30, right: 20, bottom: 50, left: 60}, - color = nv.utils.defaultColor(), - width = null, - height = null, - showLegend = true, - noData = null, - yDomain1, - yDomain2, - getX = function(d) { return d.x }, - getY = function(d) { return d.y}, - interpolate = 'monotone', - useVoronoi = true - ; - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - var x = d3.scale.linear(), - yScale1 = d3.scale.linear(), - yScale2 = d3.scale.linear(), - - lines1 = nv.models.line().yScale(yScale1), - lines2 = nv.models.line().yScale(yScale2), - - scatters1 = nv.models.scatter().yScale(yScale1), - scatters2 = nv.models.scatter().yScale(yScale2), - - bars1 = nv.models.multiBar().stacked(false).yScale(yScale1), - bars2 = nv.models.multiBar().stacked(false).yScale(yScale2), - - stack1 = nv.models.stackedArea().yScale(yScale1), - stack2 = nv.models.stackedArea().yScale(yScale2), - - xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5), - yAxis1 = nv.models.axis().scale(yScale1).orient('left'), - yAxis2 = nv.models.axis().scale(yScale2).orient('right'), - - legend = nv.models.legend().height(30), - tooltip = nv.models.tooltip(), - dispatch = d3.dispatch(); - - function chart(selection) { - selection.each(function(data) { - var container = d3.select(this), - that = this; - nv.utils.initSVG(container); - - chart.update = function() { container.transition().call(chart); }; - chart.container = this; - - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); - - var dataLines1 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 1}); - var dataLines2 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 2}); - var dataScatters1 = data.filter(function(d) {return d.type == 'scatter' && d.yAxis == 1}); - var dataScatters2 = data.filter(function(d) {return d.type == 'scatter' && d.yAxis == 2}); - var dataBars1 = data.filter(function(d) {return d.type == 'bar' && d.yAxis == 1}); - var dataBars2 = data.filter(function(d) {return d.type == 'bar' && d.yAxis == 2}); - var dataStack1 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 1}); - var dataStack2 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 2}); - - // Display noData message if there's nothing to show. - if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { - nv.utils.noData(chart, container); - return chart; - } else { - container.selectAll('.nv-noData').remove(); - } - - var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1}) - .map(function(d) { - return d.values.map(function(d,i) { - return { x: d.x, y: d.y } - }) - }); - - var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2}) - .map(function(d) { - return d.values.map(function(d,i) { - return { x: d.x, y: d.y } - }) - }); - - x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } )) - .range([0, availableWidth]); - - var wrap = container.selectAll('g.wrap.multiChart').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g'); - - gEnter.append('g').attr('class', 'nv-x nv-axis'); - gEnter.append('g').attr('class', 'nv-y1 nv-axis'); - gEnter.append('g').attr('class', 'nv-y2 nv-axis'); - gEnter.append('g').attr('class', 'lines1Wrap'); - gEnter.append('g').attr('class', 'lines2Wrap'); - gEnter.append('g').attr('class', 'scatters1Wrap'); - gEnter.append('g').attr('class', 'scatters2Wrap'); - gEnter.append('g').attr('class', 'bars1Wrap'); - gEnter.append('g').attr('class', 'bars2Wrap'); - gEnter.append('g').attr('class', 'stack1Wrap'); - gEnter.append('g').attr('class', 'stack2Wrap'); - gEnter.append('g').attr('class', 'legendWrap'); - - var g = wrap.select('g'); - - var color_array = data.map(function(d,i) { - return data[i].color || color(d, i); - }); - - if (showLegend) { - var legendWidth = legend.align() ? availableWidth / 2 : availableWidth; - var legendXPosition = legend.align() ? legendWidth : 0; - - legend.width(legendWidth); - legend.color(color_array); - - g.select('.legendWrap') - .datum(data.map(function(series) { - series.originalKey = series.originalKey === undefined ? series.key : series.originalKey; - series.key = series.originalKey + (series.yAxis == 1 ? '' : ' (right axis)'); - return series; - })) - .call(legend); - - if ( margin.top != legend.height()) { - margin.top = legend.height(); - availableHeight = nv.utils.availableHeight(height, container, margin); - } - - g.select('.legendWrap') - .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')'); - } - - lines1 - .width(availableWidth) - .height(availableHeight) - .interpolate(interpolate) - .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'})); - lines2 - .width(availableWidth) - .height(availableHeight) - .interpolate(interpolate) - .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'})); - scatters1 - .width(availableWidth) - .height(availableHeight) - .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'scatter'})); - scatters2 - .width(availableWidth) - .height(availableHeight) - .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'scatter'})); - bars1 - .width(availableWidth) - .height(availableHeight) - .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'})); - bars2 - .width(availableWidth) - .height(availableHeight) - .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'})); - stack1 - .width(availableWidth) - .height(availableHeight) - .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'})); - stack2 - .width(availableWidth) - .height(availableHeight) - .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'})); - - g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - var lines1Wrap = g.select('.lines1Wrap') - .datum(dataLines1.filter(function(d){return !d.disabled})); - var scatters1Wrap = g.select('.scatters1Wrap') - .datum(dataScatters1.filter(function(d){return !d.disabled})); - var bars1Wrap = g.select('.bars1Wrap') - .datum(dataBars1.filter(function(d){return !d.disabled})); - var stack1Wrap = g.select('.stack1Wrap') - .datum(dataStack1.filter(function(d){return !d.disabled})); - var lines2Wrap = g.select('.lines2Wrap') - .datum(dataLines2.filter(function(d){return !d.disabled})); - var scatters2Wrap = g.select('.scatters2Wrap') - .datum(dataScatters2.filter(function(d){return !d.disabled})); - var bars2Wrap = g.select('.bars2Wrap') - .datum(dataBars2.filter(function(d){return !d.disabled})); - var stack2Wrap = g.select('.stack2Wrap') - .datum(dataStack2.filter(function(d){return !d.disabled})); - - var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){ - return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}}) - }).concat([{x:0, y:0}]) : []; - var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){ - return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}}) - }).concat([{x:0, y:0}]) : []; - - yScale1 .domain(yDomain1 || d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } )) - .range([0, availableHeight]); - - yScale2 .domain(yDomain2 || d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } )) - .range([0, availableHeight]); - - lines1.yDomain(yScale1.domain()); - scatters1.yDomain(yScale1.domain()); - bars1.yDomain(yScale1.domain()); - stack1.yDomain(yScale1.domain()); - - lines2.yDomain(yScale2.domain()); - scatters2.yDomain(yScale2.domain()); - bars2.yDomain(yScale2.domain()); - stack2.yDomain(yScale2.domain()); - - if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);} - if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);} - - if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);} - if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);} - - if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);} - if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);} - - if(dataScatters1.length){d3.transition(scatters1Wrap).call(scatters1);} - if(dataScatters2.length){d3.transition(scatters2Wrap).call(scatters2);} - - xAxis - ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) - .tickSize(-availableHeight, 0); - - g.select('.nv-x.nv-axis') - .attr('transform', 'translate(0,' + availableHeight + ')'); - d3.transition(g.select('.nv-x.nv-axis')) - .call(xAxis); - - yAxis1 - ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) - .tickSize( -availableWidth, 0); - - - d3.transition(g.select('.nv-y1.nv-axis')) - .call(yAxis1); - - yAxis2 - ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) - .tickSize( -availableWidth, 0); - - d3.transition(g.select('.nv-y2.nv-axis')) - .call(yAxis2); - - g.select('.nv-y1.nv-axis') - .classed('nv-disabled', series1.length ? false : true) - .attr('transform', 'translate(' + x.range()[0] + ',0)'); - - g.select('.nv-y2.nv-axis') - .classed('nv-disabled', series2.length ? false : true) - .attr('transform', 'translate(' + x.range()[1] + ',0)'); - - legend.dispatch.on('stateChange', function(newState) { - chart.update(); - }); - - //============================================================ - // Event Handling/Dispatching - //------------------------------------------------------------ - - function mouseover_line(evt) { - var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1; - evt.value = evt.point.x; - evt.series = { - value: evt.point.y, - color: evt.point.color - }; - tooltip - .duration(100) - .valueFormatter(function(d, i) { - return yaxis.tickFormat()(d, i); - }) - .data(evt) - .position(evt.pos) - .hidden(false); - } - - function mouseover_scatter(evt) { - var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1; - evt.value = evt.point.x; - evt.series = { - value: evt.point.y, - color: evt.point.color - }; - tooltip - .duration(100) - .valueFormatter(function(d, i) { - return yaxis.tickFormat()(d, i); - }) - .data(evt) - .position(evt.pos) - .hidden(false); - } - - function mouseover_stack(evt) { - var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1; - evt.point['x'] = stack1.x()(evt.point); - evt.point['y'] = stack1.y()(evt.point); - tooltip - .duration(100) - .valueFormatter(function(d, i) { - return yaxis.tickFormat()(d, i); - }) - .data(evt) - .position(evt.pos) - .hidden(false); - } - - function mouseover_bar(evt) { - var yaxis = data[evt.data.series].yAxis === 2 ? yAxis2 : yAxis1; - - evt.value = bars1.x()(evt.data); - evt['series'] = { - value: bars1.y()(evt.data), - color: evt.color - }; - tooltip - .duration(0) - .valueFormatter(function(d, i) { - return yaxis.tickFormat()(d, i); - }) - .data(evt) - .hidden(false); - } - - lines1.dispatch.on('elementMouseover.tooltip', mouseover_line); - lines2.dispatch.on('elementMouseover.tooltip', mouseover_line); - lines1.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true) - }); - lines2.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true) - }); - - scatters1.dispatch.on('elementMouseover.tooltip', mouseover_scatter); - scatters2.dispatch.on('elementMouseover.tooltip', mouseover_scatter); - scatters1.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true) - }); - scatters2.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true) - }); - - stack1.dispatch.on('elementMouseover.tooltip', mouseover_stack); - stack2.dispatch.on('elementMouseover.tooltip', mouseover_stack); - stack1.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true) - }); - stack2.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true) - }); - - bars1.dispatch.on('elementMouseover.tooltip', mouseover_bar); - bars2.dispatch.on('elementMouseover.tooltip', mouseover_bar); - - bars1.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true); - }); - bars2.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true); - }); - bars1.dispatch.on('elementMousemove.tooltip', function(evt) { - tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); - }); - bars2.dispatch.on('elementMousemove.tooltip', function(evt) { - tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); - }); - - }); - - return chart; - } - - //============================================================ - // Global getters and setters - //------------------------------------------------------------ - - chart.dispatch = dispatch; - chart.lines1 = lines1; - chart.lines2 = lines2; - chart.scatters1 = scatters1; - chart.scatters2 = scatters2; - chart.bars1 = bars1; - chart.bars2 = bars2; - chart.stack1 = stack1; - chart.stack2 = stack2; - chart.xAxis = xAxis; - chart.yAxis1 = yAxis1; - chart.yAxis2 = yAxis2; - chart.tooltip = tooltip; - - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, - yDomain1: {get: function(){return yDomain1;}, set: function(_){yDomain1=_;}}, - yDomain2: {get: function(){return yDomain2;}, set: function(_){yDomain2=_;}}, - noData: {get: function(){return noData;}, set: function(_){noData=_;}}, - interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}}, - - // deprecated options - tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); - tooltip.enabled(!!_); - }}, - tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); - tooltip.contentGenerator(_); - }}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - }}, - x: {get: function(){return getX;}, set: function(_){ - getX = _; - lines1.x(_); - lines2.x(_); - scatters1.x(_); - scatters2.x(_); - bars1.x(_); - bars2.x(_); - stack1.x(_); - stack2.x(_); - }}, - y: {get: function(){return getY;}, set: function(_){ - getY = _; - lines1.y(_); - lines2.y(_); - scatters1.y(_); - scatters2.y(_); - stack1.y(_); - stack2.y(_); - bars1.y(_); - bars2.y(_); - }}, - useVoronoi: {get: function(){return useVoronoi;}, set: function(_){ - useVoronoi=_; - lines1.useVoronoi(_); - lines2.useVoronoi(_); - stack1.useVoronoi(_); - stack2.useVoronoi(_); - }} - }); - - nv.utils.initOptions(chart); - - return chart; - }; - - - nv.models.ohlcBar = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = null - , height = null - , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one - , container = null - , x = d3.scale.linear() - , y = d3.scale.linear() - , getX = function(d) { return d.x } - , getY = function(d) { return d.y } - , getOpen = function(d) { return d.open } - , getClose = function(d) { return d.close } - , getHigh = function(d) { return d.high } - , getLow = function(d) { return d.low } - , forceX = [] - , forceY = [] - , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart - , clipEdge = true - , color = nv.utils.defaultColor() - , interactive = false - , xDomain - , yDomain - , xRange - , yRange - , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove') - ; - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - function chart(selection) { - selection.each(function(data) { - container = d3.select(this); - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); - - nv.utils.initSVG(container); - - // ohlc bar width. - var w = (availableWidth / data[0].values.length) * .9; - - // Setup Scales - x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) )); - - if (padData) - x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); - else - x.range(xRange || [5 + w/2, availableWidth - w/2 - 5]); - - y.domain(yDomain || [ - d3.min(data[0].values.map(getLow).concat(forceY)), - d3.max(data[0].values.map(getHigh).concat(forceY)) - ] - ).range(yRange || [availableHeight, 0]); - - // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point - if (x.domain()[0] === x.domain()[1]) - x.domain()[0] ? - x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) - : x.domain([-1,1]); - - if (y.domain()[0] === y.domain()[1]) - y.domain()[0] ? - y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) - : y.domain([-1,1]); - - // Setup containers and skeleton of chart - var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar'); - var defsEnter = wrapEnter.append('defs'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); - - gEnter.append('g').attr('class', 'nv-ticks'); - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - container - .on('click', function(d,i) { - dispatch.chartClick({ - data: d, - index: i, - pos: d3.event, - id: id - }); - }); - - defsEnter.append('clipPath') - .attr('id', 'nv-chart-clip-path-' + id) - .append('rect'); - - wrap.select('#nv-chart-clip-path-' + id + ' rect') - .attr('width', availableWidth) - .attr('height', availableHeight); - - g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : ''); - - var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick') - .data(function(d) { return d }); - ticks.exit().remove(); - - ticks.enter().append('path') - .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i }) - .attr('d', function(d,i) { - return 'm0,0l0,' - + (y(getOpen(d,i)) - - y(getHigh(d,i))) - + 'l' - + (-w/2) - + ',0l' - + (w/2) - + ',0l0,' - + (y(getLow(d,i)) - y(getOpen(d,i))) - + 'l0,' - + (y(getClose(d,i)) - - y(getLow(d,i))) - + 'l' - + (w/2) - + ',0l' - + (-w/2) - + ',0z'; - }) - .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; }) - .attr('fill', function(d,i) { return color[0]; }) - .attr('stroke', function(d,i) { return color[0]; }) - .attr('x', 0 ) - .attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) }) - .attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) }); - - // the bar colors are controlled by CSS currently - ticks.attr('class', function(d,i,j) { - return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i; - }); - - d3.transition(ticks) - .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; }) - .attr('d', function(d,i) { - var w = (availableWidth / data[0].values.length) * .9; - return 'm0,0l0,' - + (y(getOpen(d,i)) - - y(getHigh(d,i))) - + 'l' - + (-w/2) - + ',0l' - + (w/2) - + ',0l0,' - + (y(getLow(d,i)) - - y(getOpen(d,i))) - + 'l0,' - + (y(getClose(d,i)) - - y(getLow(d,i))) - + 'l' - + (w/2) - + ',0l' - + (-w/2) - + ',0z'; - }); - }); - - return chart; - } - - - //Create methods to allow outside functions to highlight a specific bar. - chart.highlightPoint = function(pointIndex, isHoverOver) { - chart.clearHighlights(); - container.select(".nv-ohlcBar .nv-tick-0-" + pointIndex) - .classed("hover", isHoverOver) - ; - }; - - chart.clearHighlights = function() { - container.select(".nv-ohlcBar .nv-tick.hover") - .classed("hover", false) - ; - }; - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - xScale: {get: function(){return x;}, set: function(_){x=_;}}, - yScale: {get: function(){return y;}, set: function(_){y=_;}}, - xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, - yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, - xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, - yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, - forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}}, - forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, - padData: {get: function(){return padData;}, set: function(_){padData=_;}}, - clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, - id: {get: function(){return id;}, set: function(_){id=_;}}, - interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}}, - - x: {get: function(){return getX;}, set: function(_){getX=_;}}, - y: {get: function(){return getY;}, set: function(_){getY=_;}}, - open: {get: function(){return getOpen();}, set: function(_){getOpen=_;}}, - close: {get: function(){return getClose();}, set: function(_){getClose=_;}}, - high: {get: function(){return getHigh;}, set: function(_){getHigh=_;}}, - low: {get: function(){return getLow;}, set: function(_){getLow=_;}}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top != undefined ? _.top : margin.top; - margin.right = _.right != undefined ? _.right : margin.right; - margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom; - margin.left = _.left != undefined ? _.left : margin.left; - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - }} - }); - - nv.utils.initOptions(chart); - return chart; - }; -// Code adapted from Jason Davies' "Parallel Coordinates" -// http://bl.ocks.org/jasondavies/1341281 - nv.models.parallelCoordinates = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var margin = {top: 30, right: 0, bottom: 10, left: 0} - , width = null - , height = null - , x = d3.scale.ordinal() - , y = {} - , dimensionNames = [] - , dimensionFormats = [] - , color = nv.utils.defaultColor() - , filters = [] - , active = [] - , dragging = [] - , lineTension = 1 - , dispatch = d3.dispatch('brush', 'elementMouseover', 'elementMouseout') - ; - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - function chart(selection) { - selection.each(function(data) { - var container = d3.select(this); - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); - - nv.utils.initSVG(container); - - active = data; //set all active before first brush call - - // Setup Scales - x.rangePoints([0, availableWidth], 1).domain(dimensionNames); - - //Set as true if all values on an axis are missing. - var onlyNanValues = {}; - // Extract the list of dimensions and create a scale for each. - dimensionNames.forEach(function(d) { - var extent = d3.extent(data, function(p) { return +p[d]; }); - onlyNanValues[d] = false; - //If there is no values to display on an axis, set the extent to 0 - if (extent[0] === undefined) { - onlyNanValues[d] = true; - extent[0] = 0; - extent[1] = 0; - } - //Scale axis if there is only one value - if (extent[0] === extent[1]) { - extent[0] = extent[0] - 1; - extent[1] = extent[1] + 1; - } - //Use 90% of (availableHeight - 12) for the axis range, 12 reprensenting the space necessary to display "undefined values" text. - //The remaining 10% are used to display the missingValue line. - y[d] = d3.scale.linear() - .domain(extent) - .range([(availableHeight - 12) * 0.9, 0]); - - y[d].brush = d3.svg.brush().y(y[d]).on('brush', brush); - - return d != 'name'; - }); - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-parallelCoordinates').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-parallelCoordinates'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); - - gEnter.append('g').attr('class', 'nv-parallelCoordinates background'); - gEnter.append('g').attr('class', 'nv-parallelCoordinates foreground'); - gEnter.append('g').attr('class', 'nv-parallelCoordinates missingValuesline'); - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - var line = d3.svg.line().interpolate('cardinal').tension(lineTension), - axis = d3.svg.axis().orient('left'), - axisDrag = d3.behavior.drag() - .on('dragstart', dragStart) - .on('drag', dragMove) - .on('dragend', dragEnd); - - //Add missing value line at the bottom of the chart - var missingValuesline, missingValueslineText; - var step = x.range()[1] - x.range()[0]; - var axisWithMissingValues = []; - var lineData = [0 + step / 2, availableHeight - 12, availableWidth - step / 2, availableHeight - 12]; - missingValuesline = wrap.select('.missingValuesline').selectAll('line').data([lineData]); - missingValuesline.enter().append('line'); - missingValuesline.exit().remove(); - missingValuesline.attr("x1", function(d) { return d[0]; }) - .attr("y1", function(d) { return d[1]; }) - .attr("x2", function(d) { return d[2]; }) - .attr("y2", function(d) { return d[3]; }); - - //Add the text "undefined values" under the missing value line - missingValueslineText = wrap.select('.missingValuesline').selectAll('text').data(["undefined values"]); - missingValueslineText.append('text').data(["undefined values"]); - missingValueslineText.enter().append('text'); - missingValueslineText.exit().remove(); - missingValueslineText.attr("y", availableHeight) - //To have the text right align with the missingValues line, substract 92 representing the text size. - .attr("x", availableWidth - 92 - step / 2) - .text(function(d) { return d; }); - - // Add grey background lines for context. - var background = wrap.select('.background').selectAll('path').data(data); - background.enter().append('path'); - background.exit().remove(); - background.attr('d', path); - - // Add blue foreground lines for focus. - var foreground = wrap.select('.foreground').selectAll('path').data(data); - foreground.enter().append('path') - foreground.exit().remove(); - foreground.attr('d', path).attr('stroke', color); - foreground.on("mouseover", function (d, i) { - d3.select(this).classed('hover', true); - dispatch.elementMouseover({ - label: d.name, - data: d.data, - index: i, - pos: [d3.mouse(this.parentNode)[0], d3.mouse(this.parentNode)[1]] - }); - - }); - foreground.on("mouseout", function (d, i) { - d3.select(this).classed('hover', false); - dispatch.elementMouseout({ - label: d.name, - data: d.data, - index: i - }); - }); - - // Add a group element for each dimension. - var dimensions = g.selectAll('.dimension').data(dimensionNames); - var dimensionsEnter = dimensions.enter().append('g').attr('class', 'nv-parallelCoordinates dimension'); - dimensionsEnter.append('g').attr('class', 'nv-parallelCoordinates nv-axis'); - dimensionsEnter.append('g').attr('class', 'nv-parallelCoordinates-brush'); - dimensionsEnter.append('text').attr('class', 'nv-parallelCoordinates nv-label'); - - dimensions.attr('transform', function(d) { return 'translate(' + x(d) + ',0)'; }); - dimensions.exit().remove(); - - // Add an axis and title. - dimensions.select('.nv-label') - .style("cursor", "move") - .attr('dy', '-1em') - .attr('text-anchor', 'middle') - .text(String) - .on("mouseover", function(d, i) { - dispatch.elementMouseover({ - dim: d, - pos: [d3.mouse(this.parentNode.parentNode)[0], d3.mouse(this.parentNode.parentNode)[1]] - }); - }) - .on("mouseout", function(d, i) { - dispatch.elementMouseout({ - dim: d - }); - }) - .call(axisDrag); - - dimensions.select('.nv-axis') - .each(function (d, i) { - d3.select(this).call(axis.scale(y[d]).tickFormat(d3.format(dimensionFormats[i]))); - }); - - dimensions.select('.nv-parallelCoordinates-brush') - .each(function (d) { - d3.select(this).call(y[d].brush); - }) - .selectAll('rect') - .attr('x', -8) - .attr('width', 16); - - // Returns the path for a given data point. - function path(d) { - return line(dimensionNames.map(function (p) { - //If value if missing, put the value on the missing value line - if(isNaN(d[p]) || isNaN(parseFloat(d[p]))) { - var domain = y[p].domain(); - var range = y[p].range(); - var min = domain[0] - (domain[1] - domain[0]) / 9; - - //If it's not already the case, allow brush to select undefined values - if(axisWithMissingValues.indexOf(p) < 0) { - - var newscale = d3.scale.linear().domain([min, domain[1]]).range([availableHeight - 12, range[1]]); - y[p].brush.y(newscale); - axisWithMissingValues.push(p); - } - - return [x(p), y[p](min)]; - } - - //If parallelCoordinate contain missing values show the missing values line otherwise, hide it. - if(axisWithMissingValues.length > 0) { - missingValuesline.style("display", "inline"); - missingValueslineText.style("display", "inline"); - } else { - missingValuesline.style("display", "none"); - missingValueslineText.style("display", "none"); - } - - return [x(p), y[p](d[p])]; - })); - } - - // Handles a brush event, toggling the display of foreground lines. - function brush() { - var actives = dimensionNames.filter(function(p) { return !y[p].brush.empty(); }), - extents = actives.map(function(p) { return y[p].brush.extent(); }); - - filters = []; //erase current filters - actives.forEach(function(d,i) { - filters[i] = { - dimension: d, - extent: extents[i] - } - }); - - active = []; //erase current active list - foreground.style('display', function(d) { - var isActive = actives.every(function(p, i) { - if(isNaN(d[p]) && extents[i][0] == y[p].brush.y().domain()[0]) return true; - return extents[i][0] <= d[p] && d[p] <= extents[i][1]; - }); - if (isActive) active.push(d); - return isActive ? null : 'none'; - }); - - dispatch.brush({ - filters: filters, - active: active - }); - } - - function dragStart(d, i) { - dragging[d] = this.parentNode.__origin__ = x(d); - background.attr("visibility", "hidden"); - - } - - function dragMove(d, i) { - dragging[d] = Math.min(availableWidth, Math.max(0, this.parentNode.__origin__ += d3.event.x)); - foreground.attr("d", path); - dimensionNames.sort(function (a, b) { return position(a) - position(b); }); - x.domain(dimensionNames); - dimensions.attr("transform", function(d) { return "translate(" + position(d) + ")"; }); - } - - function dragEnd(d, i) { - delete this.parentNode.__origin__; - delete dragging[d]; - d3.select(this.parentNode).attr("transform", "translate(" + x(d) + ")"); - foreground - .attr("d", path); - background - .attr("d", path) - .attr("visibility", null); - - } - - function position(d) { - var v = dragging[d]; - return v == null ? x(d) : v; - } - }); - - return chart; - } - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width= _;}}, - height: {get: function(){return height;}, set: function(_){height= _;}}, - dimensionNames: {get: function() { return dimensionNames;}, set: function(_){dimensionNames= _;}}, - dimensionFormats : {get: function(){return dimensionFormats;}, set: function (_){dimensionFormats=_;}}, - lineTension: {get: function(){return lineTension;}, set: function(_){lineTension = _;}}, - - // deprecated options - dimensions: {get: function (){return dimensionNames;}, set: function(_){ - // deprecated after 1.8.1 - nv.deprecated('dimensions', 'use dimensionNames instead'); - dimensionNames = _; - }}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - }} - }); - - nv.utils.initOptions(chart); - return chart; - }; - nv.models.pie = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = 500 - , height = 500 - , getX = function(d) { return d.x } - , getY = function(d) { return d.y } - , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one - , container = null - , color = nv.utils.defaultColor() - , valueFormat = d3.format(',.2f') - , showLabels = true - , labelsOutside = false - , labelType = "key" - , labelThreshold = .02 //if slice percentage is under this, don't show label - , donut = false - , title = false - , growOnHover = true - , titleOffset = 0 - , labelSunbeamLayout = false - , startAngle = false - , padAngle = false - , endAngle = false - , cornerRadius = 0 - , donutRatio = 0.5 - , arcsRadius = [] - , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') - ; - - var arcs = []; - var arcsOver = []; - - //============================================================ - // chart function - //------------------------------------------------------------ - - var renderWatch = nv.utils.renderWatch(dispatch); - - function chart(selection) { - renderWatch.reset(); - selection.each(function(data) { - var availableWidth = width - margin.left - margin.right - , availableHeight = height - margin.top - margin.bottom - , radius = Math.min(availableWidth, availableHeight) / 2 - , arcsRadiusOuter = [] - , arcsRadiusInner = [] - ; - - container = d3.select(this) - if (arcsRadius.length === 0) { - var outer = radius - radius / 5; - var inner = donutRatio * radius; - for (var i = 0; i < data[0].length; i++) { - arcsRadiusOuter.push(outer); - arcsRadiusInner.push(inner); - } - } else { - arcsRadiusOuter = arcsRadius.map(function (d) { return (d.outer - d.outer / 5) * radius; }); - arcsRadiusInner = arcsRadius.map(function (d) { return (d.inner - d.inner / 5) * radius; }); - donutRatio = d3.min(arcsRadius.map(function (d) { return (d.inner - d.inner / 5); })); - } - nv.utils.initSVG(container); - - // Setup containers and skeleton of chart - var wrap = container.selectAll('.nv-wrap.nv-pie').data(data); - var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); - var g_pie = gEnter.append('g').attr('class', 'nv-pie'); - gEnter.append('g').attr('class', 'nv-pieLabels'); - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')'); - g.select('.nv-pieLabels').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')'); - - // - container.on('click', function(d,i) { - dispatch.chartClick({ - data: d, - index: i, - pos: d3.event, - id: id - }); - }); - - arcs = []; - arcsOver = []; - for (var i = 0; i < data[0].length; i++) { - - var arc = d3.svg.arc().outerRadius(arcsRadiusOuter[i]); - var arcOver = d3.svg.arc().outerRadius(arcsRadiusOuter[i] + 5); - - if (startAngle !== false) { - arc.startAngle(startAngle); - arcOver.startAngle(startAngle); - } - if (endAngle !== false) { - arc.endAngle(endAngle); - arcOver.endAngle(endAngle); - } - if (donut) { - arc.innerRadius(arcsRadiusInner[i]); - arcOver.innerRadius(arcsRadiusInner[i]); - } - - if (arc.cornerRadius && cornerRadius) { - arc.cornerRadius(cornerRadius); - arcOver.cornerRadius(cornerRadius); - } - - arcs.push(arc); - arcsOver.push(arcOver); - } - - // Setup the Pie chart and choose the data element - var pie = d3.layout.pie() - .sort(null) - .value(function(d) { return d.disabled ? 0 : getY(d) }); - - // padAngle added in d3 3.5 - if (pie.padAngle && padAngle) { - pie.padAngle(padAngle); - } - - // if title is specified and donut, put it in the middle - if (donut && title) { - g_pie.append("text").attr('class', 'nv-pie-title'); - - wrap.select('.nv-pie-title') - .style("text-anchor", "middle") - .text(function (d) { - return title; - }) - .style("font-size", (Math.min(availableWidth, availableHeight)) * donutRatio * 2 / (title.length + 2) + "px") - .attr("dy", "0.35em") // trick to vertically center text - .attr('transform', function(d, i) { - return 'translate(0, '+ titleOffset + ')'; - }); - } - - var slices = wrap.select('.nv-pie').selectAll('.nv-slice').data(pie); - var pieLabels = wrap.select('.nv-pieLabels').selectAll('.nv-label').data(pie); - - slices.exit().remove(); - pieLabels.exit().remove(); - - var ae = slices.enter().append('g'); - ae.attr('class', 'nv-slice'); - ae.on('mouseover', function(d, i) { - d3.select(this).classed('hover', true); - if (growOnHover) { - d3.select(this).select("path").transition() - .duration(70) - .attr("d", arcsOver[i]); - } - dispatch.elementMouseover({ - data: d.data, - index: i, - color: d3.select(this).style("fill") - }); - }); - ae.on('mouseout', function(d, i) { - d3.select(this).classed('hover', false); - if (growOnHover) { - d3.select(this).select("path").transition() - .duration(50) - .attr("d", arcs[i]); - } - dispatch.elementMouseout({data: d.data, index: i}); - }); - ae.on('mousemove', function(d, i) { - dispatch.elementMousemove({data: d.data, index: i}); - }); - ae.on('click', function(d, i) { - dispatch.elementClick({ - data: d.data, - index: i, - color: d3.select(this).style("fill") - }); - }); - ae.on('dblclick', function(d, i) { - dispatch.elementDblClick({ - data: d.data, - index: i, - color: d3.select(this).style("fill") - }); - }); - - slices.attr('fill', function(d,i) { return color(d.data, i); }); - slices.attr('stroke', function(d,i) { return color(d.data, i); }); - - var paths = ae.append('path').each(function(d) { - this._current = d; - }); - - slices.select('path') - .transition() - .attr('d', function (d, i) { return arcs[i](d); }) - .attrTween('d', arcTween); - - if (showLabels) { - // This does the normal label - var labelsArc = []; - for (var i = 0; i < data[0].length; i++) { - labelsArc.push(arcs[i]); - - if (labelsOutside) { - if (donut) { - labelsArc[i] = d3.svg.arc().outerRadius(arcs[i].outerRadius()); - if (startAngle !== false) labelsArc[i].startAngle(startAngle); - if (endAngle !== false) labelsArc[i].endAngle(endAngle); - } - } else if (!donut) { - labelsArc[i].innerRadius(0); - } - } - - pieLabels.enter().append("g").classed("nv-label",true).each(function(d,i) { - var group = d3.select(this); - - group.attr('transform', function (d, i) { - if (labelSunbeamLayout) { - d.outerRadius = arcsRadiusOuter[i] + 10; // Set Outer Coordinate - d.innerRadius = arcsRadiusOuter[i] + 15; // Set Inner Coordinate - var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI); - if ((d.startAngle + d.endAngle) / 2 < Math.PI) { - rotateAngle -= 90; - } else { - rotateAngle += 90; - } - return 'translate(' + labelsArc[i].centroid(d) + ') rotate(' + rotateAngle + ')'; - } else { - d.outerRadius = radius + 10; // Set Outer Coordinate - d.innerRadius = radius + 15; // Set Inner Coordinate - return 'translate(' + labelsArc[i].centroid(d) + ')' - } - }); - - group.append('rect') - .style('stroke', '#fff') - .style('fill', '#fff') - .attr("rx", 3) - .attr("ry", 3); - - group.append('text') - .style('text-anchor', labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle') //center the text on it's origin or begin/end if orthogonal aligned - .style('fill', '#000') - }); - - var labelLocationHash = {}; - var avgHeight = 14; - var avgWidth = 140; - var createHashKey = function(coordinates) { - return Math.floor(coordinates[0]/avgWidth) * avgWidth + ',' + Math.floor(coordinates[1]/avgHeight) * avgHeight; - }; - - pieLabels.watchTransition(renderWatch, 'pie labels').attr('transform', function (d, i) { - if (labelSunbeamLayout) { - d.outerRadius = arcsRadiusOuter[i] + 10; // Set Outer Coordinate - d.innerRadius = arcsRadiusOuter[i] + 15; // Set Inner Coordinate - var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI); - if ((d.startAngle + d.endAngle) / 2 < Math.PI) { - rotateAngle -= 90; - } else { - rotateAngle += 90; - } - return 'translate(' + labelsArc[i].centroid(d) + ') rotate(' + rotateAngle + ')'; - } else { - d.outerRadius = radius + 10; // Set Outer Coordinate - d.innerRadius = radius + 15; // Set Inner Coordinate - - /* - Overlapping pie labels are not good. What this attempts to do is, prevent overlapping. - Each label location is hashed, and if a hash collision occurs, we assume an overlap. - Adjust the label's y-position to remove the overlap. - */ - var center = labelsArc[i].centroid(d); - if (d.value) { - var hashKey = createHashKey(center); - if (labelLocationHash[hashKey]) { - center[1] -= avgHeight; - } - labelLocationHash[createHashKey(center)] = true; - } - return 'translate(' + center + ')' - } - }); - - pieLabels.select(".nv-label text") - .style('text-anchor', function(d,i) { - //center the text on it's origin or begin/end if orthogonal aligned - return labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle'; - }) - .text(function(d, i) { - var percent = (d.endAngle - d.startAngle) / (2 * Math.PI); - var label = ''; - if (!d.value || percent < labelThreshold) return ''; - - if(typeof labelType === 'function') { - label = labelType(d, i, { - 'key': getX(d.data), - 'value': getY(d.data), - 'percent': valueFormat(percent) - }); - } else { - switch (labelType) { - case 'key': - label = getX(d.data); - break; - case 'value': - label = valueFormat(getY(d.data)); - break; - case 'percent': - label = d3.format('%')(percent); - break; - } - } - return label; - }) - ; - } - - - // Computes the angle of an arc, converting from radians to degrees. - function angle(d) { - var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90; - return a > 90 ? a - 180 : a; - } - - function arcTween(a, idx) { - a.endAngle = isNaN(a.endAngle) ? 0 : a.endAngle; - a.startAngle = isNaN(a.startAngle) ? 0 : a.startAngle; - if (!donut) a.innerRadius = 0; - var i = d3.interpolate(this._current, a); - this._current = i(0); - return function (t) { - return arcs[idx](i(t)); - }; - } - }); - - renderWatch.renderEnd('pie immediate'); - return chart; - } - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - arcsRadius: { get: function () { return arcsRadius; }, set: function (_) { arcsRadius = _; } }, - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=_;}}, - title: {get: function(){return title;}, set: function(_){title=_;}}, - titleOffset: {get: function(){return titleOffset;}, set: function(_){titleOffset=_;}}, - labelThreshold: {get: function(){return labelThreshold;}, set: function(_){labelThreshold=_;}}, - valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}}, - x: {get: function(){return getX;}, set: function(_){getX=_;}}, - id: {get: function(){return id;}, set: function(_){id=_;}}, - endAngle: {get: function(){return endAngle;}, set: function(_){endAngle=_;}}, - startAngle: {get: function(){return startAngle;}, set: function(_){startAngle=_;}}, - padAngle: {get: function(){return padAngle;}, set: function(_){padAngle=_;}}, - cornerRadius: {get: function(){return cornerRadius;}, set: function(_){cornerRadius=_;}}, - donutRatio: {get: function(){return donutRatio;}, set: function(_){donutRatio=_;}}, - labelsOutside: {get: function(){return labelsOutside;}, set: function(_){labelsOutside=_;}}, - labelSunbeamLayout: {get: function(){return labelSunbeamLayout;}, set: function(_){labelSunbeamLayout=_;}}, - donut: {get: function(){return donut;}, set: function(_){donut=_;}}, - growOnHover: {get: function(){return growOnHover;}, set: function(_){growOnHover=_;}}, - - // depreciated after 1.7.1 - pieLabelsOutside: {get: function(){return labelsOutside;}, set: function(_){ - labelsOutside=_; - nv.deprecated('pieLabelsOutside', 'use labelsOutside instead'); - }}, - // depreciated after 1.7.1 - donutLabelsOutside: {get: function(){return labelsOutside;}, set: function(_){ - labelsOutside=_; - nv.deprecated('donutLabelsOutside', 'use labelsOutside instead'); - }}, - // deprecated after 1.7.1 - labelFormat: {get: function(){ return valueFormat;}, set: function(_) { - valueFormat=_; - nv.deprecated('labelFormat','use valueFormat instead'); - }}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = typeof _.top != 'undefined' ? _.top : margin.top; - margin.right = typeof _.right != 'undefined' ? _.right : margin.right; - margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; - margin.left = typeof _.left != 'undefined' ? _.left : margin.left; - }}, - y: {get: function(){return getY;}, set: function(_){ - getY=d3.functor(_); - }}, - color: {get: function(){return color;}, set: function(_){ - color=nv.utils.getColor(_); - }}, - labelType: {get: function(){return labelType;}, set: function(_){ - labelType= _ || 'key'; - }} - }); - - nv.utils.initOptions(chart); - return chart; - }; - nv.models.pieChart = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var pie = nv.models.pie(); - var legend = nv.models.legend(); - var tooltip = nv.models.tooltip(); - - var margin = {top: 30, right: 20, bottom: 20, left: 20} - , width = null - , height = null - , showLegend = true - , legendPosition = "top" - , color = nv.utils.defaultColor() - , state = nv.utils.state() - , defaultState = null - , noData = null - , duration = 250 - , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd') - ; - - tooltip - .headerEnabled(false) - .duration(0) - .valueFormatter(function(d, i) { - return pie.valueFormat()(d, i); - }); - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - var renderWatch = nv.utils.renderWatch(dispatch); - - var stateGetter = function(data) { - return function(){ - return { - active: data.map(function(d) { return !d.disabled }) - }; - } - }; - - var stateSetter = function(data) { - return function(state) { - if (state.active !== undefined) { - data.forEach(function (series, i) { - series.disabled = !state.active[i]; - }); - } - } - }; - - //============================================================ - // Chart function - //------------------------------------------------------------ - - function chart(selection) { - renderWatch.reset(); - renderWatch.models(pie); - - selection.each(function(data) { - var container = d3.select(this); - nv.utils.initSVG(container); - - var that = this; - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); - - chart.update = function() { container.transition().call(chart); }; - chart.container = this; - - state.setter(stateSetter(data), chart.update) - .getter(stateGetter(data)) - .update(); - - //set state.disabled - state.disabled = data.map(function(d) { return !!d.disabled }); - - if (!defaultState) { - var key; - defaultState = {}; - for (key in state) { - if (state[key] instanceof Array) - defaultState[key] = state[key].slice(0); - else - defaultState[key] = state[key]; - } - } - - // Display No Data message if there's nothing to show. - if (!data || !data.length) { - nv.utils.noData(chart, container); - return chart; - } else { - container.selectAll('.nv-noData').remove(); - } - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g'); - var g = wrap.select('g'); - - gEnter.append('g').attr('class', 'nv-pieWrap'); - gEnter.append('g').attr('class', 'nv-legendWrap'); - - // Legend - if (showLegend) { - if (legendPosition === "top") { - legend.width( availableWidth ).key(pie.x()); - - wrap.select('.nv-legendWrap') - .datum(data) - .call(legend); - - if ( margin.top != legend.height()) { - margin.top = legend.height(); - availableHeight = nv.utils.availableHeight(height, container, margin); - } - - wrap.select('.nv-legendWrap') - .attr('transform', 'translate(0,' + (-margin.top) +')'); - } else if (legendPosition === "right") { - var legendWidth = nv.models.legend().width(); - if (availableWidth / 2 < legendWidth) { - legendWidth = (availableWidth / 2) - } - legend.height(availableHeight).key(pie.x()); - legend.width(legendWidth); - availableWidth -= legend.width(); - - wrap.select('.nv-legendWrap') - .datum(data) - .call(legend) - .attr('transform', 'translate(' + (availableWidth) +',0)'); - } - } - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - // Main Chart Component(s) - pie.width(availableWidth).height(availableHeight); - var pieWrap = g.select('.nv-pieWrap').datum([data]); - d3.transition(pieWrap).call(pie); - - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ - - legend.dispatch.on('stateChange', function(newState) { - for (var key in newState) { - state[key] = newState[key]; - } - dispatch.stateChange(state); - chart.update(); - }); - - // Update chart from a state object passed to event handler - dispatch.on('changeState', function(e) { - if (typeof e.disabled !== 'undefined') { - data.forEach(function(series,i) { - series.disabled = e.disabled[i]; - }); - state.disabled = e.disabled; - } - chart.update(); - }); - }); - - renderWatch.renderEnd('pieChart immediate'); - return chart; - } - - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ - - pie.dispatch.on('elementMouseover.tooltip', function(evt) { - evt['series'] = { - key: chart.x()(evt.data), - value: chart.y()(evt.data), - color: evt.color - }; - tooltip.data(evt).hidden(false); - }); - - pie.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true); - }); - - pie.dispatch.on('elementMousemove.tooltip', function(evt) { - tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); - }); - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - // expose chart's sub-components - chart.legend = legend; - chart.dispatch = dispatch; - chart.pie = pie; - chart.tooltip = tooltip; - chart.options = nv.utils.optionsFunc.bind(chart); - - // use Object get/set functionality to map between vars and chart functions - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - noData: {get: function(){return noData;}, set: function(_){noData=_;}}, - showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, - legendPosition: {get: function(){return legendPosition;}, set: function(_){legendPosition=_;}}, - defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, - - // deprecated options - tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); - tooltip.enabled(!!_); - }}, - tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); - tooltip.contentGenerator(_); - }}, - - // options that require extra logic in the setter - color: {get: function(){return color;}, set: function(_){ - color = _; - legend.color(color); - pie.color(color); - }}, - duration: {get: function(){return duration;}, set: function(_){ - duration = _; - renderWatch.reset(duration); - }}, - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }} - }); - nv.utils.inheritOptions(chart, pie); - nv.utils.initOptions(chart); - return chart; - }; - - nv.models.scatter = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = null - , height = null - , color = nv.utils.defaultColor() // chooses color - , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't select one - , container = null - , x = d3.scale.linear() - , y = d3.scale.linear() - , z = d3.scale.linear() //linear because d3.svg.shape.size is treated as area - , getX = function(d) { return d.x } // accessor to get the x value - , getY = function(d) { return d.y } // accessor to get the y value - , getSize = function(d) { return d.size || 1} // accessor to get the point size - , getShape = function(d) { return d.shape || 'circle' } // accessor to get point shape - , forceX = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.) - , forceY = [] // List of numbers to Force into the Y scale - , forceSize = [] // List of numbers to Force into the Size scale - , interactive = true // If true, plots a voronoi overlay for advanced point intersection - , pointActive = function(d) { return !d.notActive } // any points that return false will be filtered out - , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart - , padDataOuter = .1 //outerPadding to imitate ordinal scale outer padding - , clipEdge = false // if true, masks points within x and y scale - , clipVoronoi = true // if true, masks each point with a circle... can turn off to slightly increase performance - , showVoronoi = false // display the voronoi areas - , clipRadius = function() { return 25 } // function to get the radius for voronoi point clips - , xDomain = null // Override x domain (skips the calculation from data) - , yDomain = null // Override y domain - , xRange = null // Override x range - , yRange = null // Override y range - , sizeDomain = null // Override point size domain - , sizeRange = null - , singlePoint = false - , dispatch = d3.dispatch('elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd') - , useVoronoi = true - , duration = 250 - ; - - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - var x0, y0, z0 // used to store previous scales - , timeoutID - , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips - , renderWatch = nv.utils.renderWatch(dispatch, duration) - , _sizeRange_def = [16, 256] - ; - - function chart(selection) { - renderWatch.reset(); - selection.each(function(data) { - container = d3.select(this); - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); - - nv.utils.initSVG(container); - - //add series index to each data point for reference - data.forEach(function(series, i) { - series.values.forEach(function(point) { - point.series = i; - }); - }); - - // Setup Scales - // remap and flatten the data for use in calculating the scales' domains - var seriesData = (xDomain && yDomain && sizeDomain) ? [] : // if we know xDomain and yDomain and sizeDomain, no need to calculate.... if Size is constant remember to set sizeDomain to speed up performance - d3.merge( - data.map(function(d) { - return d.values.map(function(d,i) { - return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) } - }) - }) - ); - - x .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x; }).concat(forceX))) - - if (padData && data[0]) - x.range(xRange || [(availableWidth * padDataOuter + availableWidth) / (2 *data[0].values.length), availableWidth - availableWidth * (1 + padDataOuter) / (2 * data[0].values.length) ]); - //x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); - else - x.range(xRange || [0, availableWidth]); - - y .domain(yDomain || d3.extent(seriesData.map(function(d) { return d.y }).concat(forceY))) - .range(yRange || [availableHeight, 0]); - - z .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize))) - .range(sizeRange || _sizeRange_def); - - // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point - singlePoint = x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]; - - if (x.domain()[0] === x.domain()[1]) - x.domain()[0] ? - x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) - : x.domain([-1,1]); - - if (y.domain()[0] === y.domain()[1]) - y.domain()[0] ? - y.domain([y.domain()[0] - y.domain()[0] * 0.01, y.domain()[1] + y.domain()[1] * 0.01]) - : y.domain([-1,1]); - - if ( isNaN(x.domain()[0])) { - x.domain([-1,1]); - } - - if ( isNaN(y.domain()[0])) { - y.domain([-1,1]); - } - - x0 = x0 || x; - y0 = y0 || y; - z0 = z0 || z; - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id); - var defsEnter = wrapEnter.append('defs'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); - - wrap.classed('nv-single-point', singlePoint); - gEnter.append('g').attr('class', 'nv-groups'); - gEnter.append('g').attr('class', 'nv-point-paths'); - wrapEnter.append('g').attr('class', 'nv-point-clips'); - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - defsEnter.append('clipPath') - .attr('id', 'nv-edge-clip-' + id) - .append('rect'); - - wrap.select('#nv-edge-clip-' + id + ' rect') - .attr('width', availableWidth) - .attr('height', (availableHeight > 0) ? availableHeight : 0); - - g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); - - function updateInteractiveLayer() { - // Always clear needs-update flag regardless of whether or not - // we will actually do anything (avoids needless invocations). - needsUpdate = false; - - if (!interactive) return false; - - // inject series and point index for reference into voronoi - if (useVoronoi === true) { - var vertices = d3.merge(data.map(function(group, groupIndex) { - return group.values - .map(function(point, pointIndex) { - // *Adding noise to make duplicates very unlikely - // *Injecting series and point index for reference - /* *Adding a 'jitter' to the points, because there's an issue in d3.geom.voronoi. - */ - var pX = getX(point,pointIndex); - var pY = getY(point,pointIndex); - - return [x(pX)+ Math.random() * 1e-4, - y(pY)+ Math.random() * 1e-4, - groupIndex, - pointIndex, point]; //temp hack to add noise until I think of a better way so there are no duplicates - }) - .filter(function(pointArray, pointIndex) { - return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct! - }) - }) - ); - - if (vertices.length == 0) return false; // No active points, we're done - if (vertices.length < 3) { - // Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work - vertices.push([x.range()[0] - 20, y.range()[0] - 20, null, null]); - vertices.push([x.range()[1] + 20, y.range()[1] + 20, null, null]); - vertices.push([x.range()[0] - 20, y.range()[0] + 20, null, null]); - vertices.push([x.range()[1] + 20, y.range()[1] - 20, null, null]); - } - - // keep voronoi sections from going more than 10 outside of graph - // to avoid overlap with other things like legend etc - var bounds = d3.geom.polygon([ - [-10,-10], - [-10,height + 10], - [width + 10,height + 10], - [width + 10,-10] - ]); - - var voronoi = d3.geom.voronoi(vertices).map(function(d, i) { - return { - 'data': bounds.clip(d), - 'series': vertices[i][2], - 'point': vertices[i][3] - } - }); - - // nuke all voronoi paths on reload and recreate them - wrap.select('.nv-point-paths').selectAll('path').remove(); - var pointPaths = wrap.select('.nv-point-paths').selectAll('path').data(voronoi); - var vPointPaths = pointPaths - .enter().append("svg:path") - .attr("d", function(d) { - if (!d || !d.data || d.data.length === 0) - return 'M 0 0'; - else - return "M" + d.data.join(",") + "Z"; - }) - .attr("id", function(d,i) { - return "nv-path-"+i; }) - .attr("clip-path", function(d,i) { return "url(#nv-clip-"+id+"-"+i+")"; }) - ; - - // good for debugging point hover issues - if (showVoronoi) { - vPointPaths.style("fill", d3.rgb(230, 230, 230)) - .style('fill-opacity', 0.4) - .style('stroke-opacity', 1) - .style("stroke", d3.rgb(200,200,200)); - } - - if (clipVoronoi) { - // voronoi sections are already set to clip, - // just create the circles with the IDs they expect - wrap.select('.nv-point-clips').selectAll('clipPath').remove(); - wrap.select('.nv-point-clips').selectAll("clipPath") - .data(vertices) - .enter().append("svg:clipPath") - .attr("id", function(d, i) { return "nv-clip-"+id+"-"+i;}) - .append("svg:circle") - .attr('cx', function(d) { return d[0]; }) - .attr('cy', function(d) { return d[1]; }) - .attr('r', clipRadius); - } - - var mouseEventCallback = function(d, mDispatch) { - if (needsUpdate) return 0; - var series = data[d.series]; - if (series === undefined) return; - var point = series.values[d.point]; - point['color'] = color(series, d.series); - - // standardize attributes for tooltip. - point['x'] = getX(point); - point['y'] = getY(point); - - // can't just get box of event node since it's actually a voronoi polygon - var box = container.node().getBoundingClientRect(); - var scrollTop = window.pageYOffset || document.documentElement.scrollTop; - var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft; - - var pos = { - left: x(getX(point, d.point)) + box.left + scrollLeft + margin.left + 10, - top: y(getY(point, d.point)) + box.top + scrollTop + margin.top + 10 - }; - - mDispatch({ - point: point, - series: series, - pos: pos, - seriesIndex: d.series, - pointIndex: d.point - }); - }; - - pointPaths - .on('click', function(d) { - mouseEventCallback(d, dispatch.elementClick); - }) - .on('dblclick', function(d) { - mouseEventCallback(d, dispatch.elementDblClick); - }) - .on('mouseover', function(d) { - mouseEventCallback(d, dispatch.elementMouseover); - }) - .on('mouseout', function(d, i) { - mouseEventCallback(d, dispatch.elementMouseout); - }); - - } else { - // add event handlers to points instead voronoi paths - wrap.select('.nv-groups').selectAll('.nv-group') - .selectAll('.nv-point') - //.data(dataWithPoints) - //.style('pointer-events', 'auto') // recativate events, disabled by css - .on('click', function(d,i) { - //nv.log('test', d, i); - if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point - var series = data[d.series], - point = series.values[i]; - - dispatch.elementClick({ - point: point, - series: series, - pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top], - seriesIndex: d.series, - pointIndex: i - }); - }) - .on('dblclick', function(d,i) { - if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point - var series = data[d.series], - point = series.values[i]; - - dispatch.elementDblClick({ - point: point, - series: series, - pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top], - seriesIndex: d.series, - pointIndex: i - }); - }) - .on('mouseover', function(d,i) { - if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point - var series = data[d.series], - point = series.values[i]; - - dispatch.elementMouseover({ - point: point, - series: series, - pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top], - seriesIndex: d.series, - pointIndex: i, - color: color(d, i) - }); - }) - .on('mouseout', function(d,i) { - if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point - var series = data[d.series], - point = series.values[i]; - - dispatch.elementMouseout({ - point: point, - series: series, - seriesIndex: d.series, - pointIndex: i, - color: color(d, i) - }); - }); - } - } - - needsUpdate = true; - var groups = wrap.select('.nv-groups').selectAll('.nv-group') - .data(function(d) { return d }, function(d) { return d.key }); - groups.enter().append('g') - .style('stroke-opacity', 1e-6) - .style('fill-opacity', 1e-6); - groups.exit() - .remove(); - groups - .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) - .classed('hover', function(d) { return d.hover }); - groups.watchTransition(renderWatch, 'scatter: groups') - .style('fill', function(d,i) { return color(d, i) }) - .style('stroke', function(d,i) { return color(d, i) }) - .style('stroke-opacity', 1) - .style('fill-opacity', .5); - - // create the points, maintaining their IDs from the original data set - var points = groups.selectAll('path.nv-point') - .data(function(d) { - return d.values.map( - function (point, pointIndex) { - return [point, pointIndex] - }).filter( - function(pointArray, pointIndex) { - return pointActive(pointArray[0], pointIndex) - }) - }); - points.enter().append('path') - .style('fill', function (d) { return d.color }) - .style('stroke', function (d) { return d.color }) - .attr('transform', function(d) { - return 'translate(' + x0(getX(d[0],d[1])) + ',' + y0(getY(d[0],d[1])) + ')' - }) - .attr('d', - nv.utils.symbol() - .type(function(d) { return getShape(d[0]); }) - .size(function(d) { return z(getSize(d[0],d[1])) }) - ); - points.exit().remove(); - groups.exit().selectAll('path.nv-point') - .watchTransition(renderWatch, 'scatter exit') - .attr('transform', function(d) { - return 'translate(' + x(getX(d[0],d[1])) + ',' + y(getY(d[0],d[1])) + ')' - }) - .remove(); - points.each(function(d) { - d3.select(this) - .classed('nv-point', true) - .classed('nv-point-' + d[1], true) - .classed('nv-noninteractive', !interactive) - .classed('hover',false) - ; - }); - points - .watchTransition(renderWatch, 'scatter points') - .attr('transform', function(d) { - //nv.log(d, getX(d[0],d[1]), x(getX(d[0],d[1]))); - return 'translate(' + x(getX(d[0],d[1])) + ',' + y(getY(d[0],d[1])) + ')' - }) - .attr('d', - nv.utils.symbol() - .type(function(d) { return getShape(d[0]); }) - .size(function(d) { return z(getSize(d[0],d[1])) }) - ); - - // Delay updating the invisible interactive layer for smoother animation - clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer - timeoutID = setTimeout(updateInteractiveLayer, 300); - //updateInteractiveLayer(); - - //store old scales for use in transitions on update - x0 = x.copy(); - y0 = y.copy(); - z0 = z.copy(); - - }); - renderWatch.renderEnd('scatter immediate'); - return chart; - } - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); - - // utility function calls provided by this chart - chart._calls = new function() { - this.clearHighlights = function () { - nv.dom.write(function() { - container.selectAll(".nv-point.hover").classed("hover", false); - }); - return null; - }; - this.highlightPoint = function (seriesIndex, pointIndex, isHoverOver) { - nv.dom.write(function() { - container.select(" .nv-series-" + seriesIndex + " .nv-point-" + pointIndex) - .classed("hover", isHoverOver); - }); - }; - }; - - // trigger calls from events too - dispatch.on('elementMouseover.point', function(d) { - if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,true); - }); - - dispatch.on('elementMouseout.point', function(d) { - if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,false); - }); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - xScale: {get: function(){return x;}, set: function(_){x=_;}}, - yScale: {get: function(){return y;}, set: function(_){y=_;}}, - pointScale: {get: function(){return z;}, set: function(_){z=_;}}, - xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, - yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, - pointDomain: {get: function(){return sizeDomain;}, set: function(_){sizeDomain=_;}}, - xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, - yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, - pointRange: {get: function(){return sizeRange;}, set: function(_){sizeRange=_;}}, - forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}}, - forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, - forcePoint: {get: function(){return forceSize;}, set: function(_){forceSize=_;}}, - interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}}, - pointActive: {get: function(){return pointActive;}, set: function(_){pointActive=_;}}, - padDataOuter: {get: function(){return padDataOuter;}, set: function(_){padDataOuter=_;}}, - padData: {get: function(){return padData;}, set: function(_){padData=_;}}, - clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, - clipVoronoi: {get: function(){return clipVoronoi;}, set: function(_){clipVoronoi=_;}}, - clipRadius: {get: function(){return clipRadius;}, set: function(_){clipRadius=_;}}, - showVoronoi: {get: function(){return showVoronoi;}, set: function(_){showVoronoi=_;}}, - id: {get: function(){return id;}, set: function(_){id=_;}}, - - - // simple functor options - x: {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}}, - y: {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}}, - pointSize: {get: function(){return getSize;}, set: function(_){getSize = d3.functor(_);}}, - pointShape: {get: function(){return getShape;}, set: function(_){getShape = d3.functor(_);}}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - duration: {get: function(){return duration;}, set: function(_){ - duration = _; - renderWatch.reset(duration); - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - }}, - useVoronoi: {get: function(){return useVoronoi;}, set: function(_){ - useVoronoi = _; - if (useVoronoi === false) { - clipVoronoi = false; - } - }} - }); - - nv.utils.initOptions(chart); - return chart; - }; - - nv.models.scatterChart = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var scatter = nv.models.scatter() - , xAxis = nv.models.axis() - , yAxis = nv.models.axis() - , legend = nv.models.legend() - , distX = nv.models.distribution() - , distY = nv.models.distribution() - , tooltip = nv.models.tooltip() - ; - - var margin = {top: 30, right: 20, bottom: 50, left: 75} - , width = null - , height = null - , container = null - , color = nv.utils.defaultColor() - , x = scatter.xScale() - , y = scatter.yScale() - , showDistX = false - , showDistY = false - , showLegend = true - , showXAxis = true - , showYAxis = true - , rightAlignYAxis = false - , state = nv.utils.state() - , defaultState = null - , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd') - , noData = null - , duration = 250 - ; - - scatter.xScale(x).yScale(y); - xAxis.orient('bottom').tickPadding(10); - yAxis - .orient((rightAlignYAxis) ? 'right' : 'left') - .tickPadding(10) - ; - distX.axis('x'); - distY.axis('y'); - tooltip - .headerFormatter(function(d, i) { - return xAxis.tickFormat()(d, i); - }) - .valueFormatter(function(d, i) { - return yAxis.tickFormat()(d, i); - }); - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - var x0, y0 - , renderWatch = nv.utils.renderWatch(dispatch, duration); - - var stateGetter = function(data) { - return function(){ - return { - active: data.map(function(d) { return !d.disabled }) - }; - } - }; - - var stateSetter = function(data) { - return function(state) { - if (state.active !== undefined) - data.forEach(function(series,i) { - series.disabled = !state.active[i]; - }); - } - }; - - function chart(selection) { - renderWatch.reset(); - renderWatch.models(scatter); - if (showXAxis) renderWatch.models(xAxis); - if (showYAxis) renderWatch.models(yAxis); - if (showDistX) renderWatch.models(distX); - if (showDistY) renderWatch.models(distY); - - selection.each(function(data) { - var that = this; - - container = d3.select(this); - nv.utils.initSVG(container); - - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); - - chart.update = function() { - if (duration === 0) - container.call(chart); - else - container.transition().duration(duration).call(chart); - }; - chart.container = this; - - state - .setter(stateSetter(data), chart.update) - .getter(stateGetter(data)) - .update(); - - // DEPRECATED set state.disableddisabled - state.disabled = data.map(function(d) { return !!d.disabled }); - - if (!defaultState) { - var key; - defaultState = {}; - for (key in state) { - if (state[key] instanceof Array) - defaultState[key] = state[key].slice(0); - else - defaultState[key] = state[key]; - } - } - - // Display noData message if there's nothing to show. - if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { - nv.utils.noData(chart, container); - renderWatch.renderEnd('scatter immediate'); - return chart; - } else { - container.selectAll('.nv-noData').remove(); - } - - // Setup Scales - x = scatter.xScale(); - y = scatter.yScale(); - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id()); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); - - // background for pointer events - gEnter.append('rect').attr('class', 'nvd3 nv-background').style("pointer-events","none"); - - gEnter.append('g').attr('class', 'nv-x nv-axis'); - gEnter.append('g').attr('class', 'nv-y nv-axis'); - gEnter.append('g').attr('class', 'nv-scatterWrap'); - gEnter.append('g').attr('class', 'nv-regressionLinesWrap'); - gEnter.append('g').attr('class', 'nv-distWrap'); - gEnter.append('g').attr('class', 'nv-legendWrap'); - - if (rightAlignYAxis) { - g.select(".nv-y.nv-axis") - .attr("transform", "translate(" + availableWidth + ",0)"); - } - - // Legend - if (showLegend) { - var legendWidth = availableWidth; - legend.width(legendWidth); - - wrap.select('.nv-legendWrap') - .datum(data) - .call(legend); - - if ( margin.top != legend.height()) { - margin.top = legend.height(); - availableHeight = nv.utils.availableHeight(height, container, margin); - } - - wrap.select('.nv-legendWrap') - .attr('transform', 'translate(0' + ',' + (-margin.top) +')'); - } - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - // Main Chart Component(s) - scatter - .width(availableWidth) - .height(availableHeight) - .color(data.map(function(d,i) { - d.color = d.color || color(d, i); - return d.color; - }).filter(function(d,i) { return !data[i].disabled })); - - wrap.select('.nv-scatterWrap') - .datum(data.filter(function(d) { return !d.disabled })) - .call(scatter); - - - wrap.select('.nv-regressionLinesWrap') - .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')'); - - var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines') - .data(function (d) { - return d; - }); - - regWrap.enter().append('g').attr('class', 'nv-regLines'); - - var regLine = regWrap.selectAll('.nv-regLine') - .data(function (d) { - return [d] - }); - - regLine.enter() - .append('line').attr('class', 'nv-regLine') - .style('stroke-opacity', 0); - - // don't add lines unless we have slope and intercept to use - regLine.filter(function(d) { - return d.intercept && d.slope; - }) - .watchTransition(renderWatch, 'scatterPlusLineChart: regline') - .attr('x1', x.range()[0]) - .attr('x2', x.range()[1]) - .attr('y1', function (d, i) { - return y(x.domain()[0] * d.slope + d.intercept) - }) - .attr('y2', function (d, i) { - return y(x.domain()[1] * d.slope + d.intercept) - }) - .style('stroke', function (d, i, j) { - return color(d, j) - }) - .style('stroke-opacity', function (d, i) { - return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1 - }); - - // Setup Axes - if (showXAxis) { - xAxis - .scale(x) - ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) - .tickSize( -availableHeight , 0); - - g.select('.nv-x.nv-axis') - .attr('transform', 'translate(0,' + y.range()[0] + ')') - .call(xAxis); - } - - if (showYAxis) { - yAxis - .scale(y) - ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) - .tickSize( -availableWidth, 0); - - g.select('.nv-y.nv-axis') - .call(yAxis); - } - - - if (showDistX) { - distX - .getData(scatter.x()) - .scale(x) - .width(availableWidth) - .color(data.map(function(d,i) { - return d.color || color(d, i); - }).filter(function(d,i) { return !data[i].disabled })); - gEnter.select('.nv-distWrap').append('g') - .attr('class', 'nv-distributionX'); - g.select('.nv-distributionX') - .attr('transform', 'translate(0,' + y.range()[0] + ')') - .datum(data.filter(function(d) { return !d.disabled })) - .call(distX); - } - - if (showDistY) { - distY - .getData(scatter.y()) - .scale(y) - .width(availableHeight) - .color(data.map(function(d,i) { - return d.color || color(d, i); - }).filter(function(d,i) { return !data[i].disabled })); - gEnter.select('.nv-distWrap').append('g') - .attr('class', 'nv-distributionY'); - g.select('.nv-distributionY') - .attr('transform', 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)') - .datum(data.filter(function(d) { return !d.disabled })) - .call(distY); - } - - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ - - legend.dispatch.on('stateChange', function(newState) { - for (var key in newState) - state[key] = newState[key]; - dispatch.stateChange(state); - chart.update(); - }); - - // Update chart from a state object passed to event handler - dispatch.on('changeState', function(e) { - if (typeof e.disabled !== 'undefined') { - data.forEach(function(series,i) { - series.disabled = e.disabled[i]; - }); - state.disabled = e.disabled; - } - chart.update(); - }); - - // mouseover needs availableHeight so we just keep scatter mouse events inside the chart block - scatter.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true); - container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex) - .attr('y1', 0); - container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex) - .attr('x2', distY.size()); - }); - - scatter.dispatch.on('elementMouseover.tooltip', function(evt) { - container.select('.nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex) - .attr('y1', evt.pos.top - availableHeight - margin.top); - container.select('.nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex) - .attr('x2', evt.pos.left + distX.size() - margin.left); - tooltip.position(evt.pos).data(evt).hidden(false); - }); - - //store old scales for use in transitions on update - x0 = x.copy(); - y0 = y.copy(); - - }); - - renderWatch.renderEnd('scatter with line immediate'); - return chart; - } - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - // expose chart's sub-components - chart.dispatch = dispatch; - chart.scatter = scatter; - chart.legend = legend; - chart.xAxis = xAxis; - chart.yAxis = yAxis; - chart.distX = distX; - chart.distY = distY; - chart.tooltip = tooltip; - - chart.options = nv.utils.optionsFunc.bind(chart); - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - container: {get: function(){return container;}, set: function(_){container=_;}}, - showDistX: {get: function(){return showDistX;}, set: function(_){showDistX=_;}}, - showDistY: {get: function(){return showDistY;}, set: function(_){showDistY=_;}}, - showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, - showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, - showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, - defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, - noData: {get: function(){return noData;}, set: function(_){noData=_;}}, - duration: {get: function(){return duration;}, set: function(_){duration=_;}}, - - // deprecated options - tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); - tooltip.enabled(!!_); - }}, - tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); - tooltip.contentGenerator(_); - }}, - tooltipXContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltipContent', 'This option is removed, put values into main tooltip.'); - }}, - tooltipYContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltipContent', 'This option is removed, put values into main tooltip.'); - }}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ - rightAlignYAxis = _; - yAxis.orient( (_) ? 'right' : 'left'); - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - legend.color(color); - distX.color(color); - distY.color(color); - }} - }); - - nv.utils.inheritOptions(chart, scatter); - nv.utils.initOptions(chart); - return chart; - }; - - nv.models.sparkline = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var margin = {top: 2, right: 0, bottom: 2, left: 0} - , width = 400 - , height = 32 - , container = null - , animate = true - , x = d3.scale.linear() - , y = d3.scale.linear() - , getX = function(d) { return d.x } - , getY = function(d) { return d.y } - , color = nv.utils.getColor(['#000']) - , xDomain - , yDomain - , xRange - , yRange - ; - - function chart(selection) { - selection.each(function(data) { - var availableWidth = width - margin.left - margin.right, - availableHeight = height - margin.top - margin.bottom; - - container = d3.select(this); - nv.utils.initSVG(container); - - // Setup Scales - x .domain(xDomain || d3.extent(data, getX )) - .range(xRange || [0, availableWidth]); - - y .domain(yDomain || d3.extent(data, getY )) - .range(yRange || [availableHeight, 0]); - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') - - var paths = wrap.selectAll('path') - .data(function(d) { return [d] }); - paths.enter().append('path'); - paths.exit().remove(); - paths - .style('stroke', function(d,i) { return d.color || color(d, i) }) - .attr('d', d3.svg.line() - .x(function(d,i) { return x(getX(d,i)) }) - .y(function(d,i) { return y(getY(d,i)) }) - ); - - // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent) - var points = wrap.selectAll('circle.nv-point') - .data(function(data) { - var yValues = data.map(function(d, i) { return getY(d,i); }); - function pointIndex(index) { - if (index != -1) { - var result = data[index]; - result.pointIndex = index; - return result; - } else { - return null; - } - } - var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])), - minPoint = pointIndex(yValues.indexOf(y.domain()[0])), - currentPoint = pointIndex(yValues.length - 1); - return [minPoint, maxPoint, currentPoint].filter(function (d) {return d != null;}); - }); - points.enter().append('circle'); - points.exit().remove(); - points - .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) }) - .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) }) - .attr('r', 2) - .attr('class', function(d,i) { - return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' : - getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue' - }); - }); - - return chart; - } - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, - yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, - xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, - yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, - xScale: {get: function(){return x;}, set: function(_){x=_;}}, - yScale: {get: function(){return y;}, set: function(_){y=_;}}, - animate: {get: function(){return animate;}, set: function(_){animate=_;}}, - - //functor options - x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}}, - y: {get: function(){return getY;}, set: function(_){getY=d3.functor(_);}}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - }} - }); - - nv.utils.initOptions(chart); - return chart; - }; - - nv.models.sparklinePlus = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var sparkline = nv.models.sparkline(); - - var margin = {top: 15, right: 100, bottom: 10, left: 50} - , width = null - , height = null - , x - , y - , index = [] - , paused = false - , xTickFormat = d3.format(',r') - , yTickFormat = d3.format(',.2f') - , showLastValue = true - , alignValue = true - , rightAlignValue = false - , noData = null - ; - - function chart(selection) { - selection.each(function(data) { - var container = d3.select(this); - nv.utils.initSVG(container); - - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); - - chart.update = function() { container.call(chart); }; - chart.container = this; - - // Display No Data message if there's nothing to show. - if (!data || !data.length) { - nv.utils.noData(chart, container) - return chart; - } else { - container.selectAll('.nv-noData').remove(); - } - - var currentValue = sparkline.y()(data[data.length-1], data.length-1); - - // Setup Scales - x = sparkline.xScale(); - y = sparkline.yScale(); - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); - - gEnter.append('g').attr('class', 'nv-sparklineWrap'); - gEnter.append('g').attr('class', 'nv-valueWrap'); - gEnter.append('g').attr('class', 'nv-hoverArea'); - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - // Main Chart Component(s) - var sparklineWrap = g.select('.nv-sparklineWrap'); - - sparkline.width(availableWidth).height(availableHeight); - sparklineWrap.call(sparkline); - - if (showLastValue) { - var valueWrap = g.select('.nv-valueWrap'); - var value = valueWrap.selectAll('.nv-currentValue') - .data([currentValue]); - - value.enter().append('text').attr('class', 'nv-currentValue') - .attr('dx', rightAlignValue ? -8 : 8) - .attr('dy', '.9em') - .style('text-anchor', rightAlignValue ? 'end' : 'start'); - - value - .attr('x', availableWidth + (rightAlignValue ? margin.right : 0)) - .attr('y', alignValue ? function (d) { - return y(d) - } : 0) - .style('fill', sparkline.color()(data[data.length - 1], data.length - 1)) - .text(yTickFormat(currentValue)); - } - - gEnter.select('.nv-hoverArea').append('rect') - .on('mousemove', sparklineHover) - .on('click', function() { paused = !paused }) - .on('mouseout', function() { index = []; updateValueLine(); }); - - g.select('.nv-hoverArea rect') - .attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' }) - .attr('width', availableWidth + margin.left + margin.right) - .attr('height', availableHeight + margin.top); - - //index is currently global (within the chart), may or may not keep it that way - function updateValueLine() { - if (paused) return; - - var hoverValue = g.selectAll('.nv-hoverValue').data(index); - - var hoverEnter = hoverValue.enter() - .append('g').attr('class', 'nv-hoverValue') - .style('stroke-opacity', 0) - .style('fill-opacity', 0); - - hoverValue.exit() - .transition().duration(250) - .style('stroke-opacity', 0) - .style('fill-opacity', 0) - .remove(); - - hoverValue - .attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' }) - .transition().duration(250) - .style('stroke-opacity', 1) - .style('fill-opacity', 1); - - if (!index.length) return; - - hoverEnter.append('line') - .attr('x1', 0) - .attr('y1', -margin.top) - .attr('x2', 0) - .attr('y2', availableHeight); - - hoverEnter.append('text').attr('class', 'nv-xValue') - .attr('x', -6) - .attr('y', -margin.top) - .attr('text-anchor', 'end') - .attr('dy', '.9em'); - - g.select('.nv-hoverValue .nv-xValue') - .text(xTickFormat(sparkline.x()(data[index[0]], index[0]))); - - hoverEnter.append('text').attr('class', 'nv-yValue') - .attr('x', 6) - .attr('y', -margin.top) - .attr('text-anchor', 'start') - .attr('dy', '.9em'); - - g.select('.nv-hoverValue .nv-yValue') - .text(yTickFormat(sparkline.y()(data[index[0]], index[0]))); - } - - function sparklineHover() { - if (paused) return; - - var pos = d3.mouse(this)[0] - margin.left; - - function getClosestIndex(data, x) { - var distance = Math.abs(sparkline.x()(data[0], 0) - x); - var closestIndex = 0; - for (var i = 0; i < data.length; i++){ - if (Math.abs(sparkline.x()(data[i], i) - x) < distance) { - distance = Math.abs(sparkline.x()(data[i], i) - x); - closestIndex = i; - } - } - return closestIndex; - } - - index = [getClosestIndex(data, Math.round(x.invert(pos)))]; - updateValueLine(); - } - - }); - - return chart; - } - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - // expose chart's sub-components - chart.sparkline = sparkline; - - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - xTickFormat: {get: function(){return xTickFormat;}, set: function(_){xTickFormat=_;}}, - yTickFormat: {get: function(){return yTickFormat;}, set: function(_){yTickFormat=_;}}, - showLastValue: {get: function(){return showLastValue;}, set: function(_){showLastValue=_;}}, - alignValue: {get: function(){return alignValue;}, set: function(_){alignValue=_;}}, - rightAlignValue: {get: function(){return rightAlignValue;}, set: function(_){rightAlignValue=_;}}, - noData: {get: function(){return noData;}, set: function(_){noData=_;}}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }} - }); - - nv.utils.inheritOptions(chart, sparkline); - nv.utils.initOptions(chart); - - return chart; - }; - - nv.models.stackedArea = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = 960 - , height = 500 - , color = nv.utils.defaultColor() // a function that computes the color - , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one - , container = null - , getX = function(d) { return d.x } // accessor to get the x value from a data point - , getY = function(d) { return d.y } // accessor to get the y value from a data point - , style = 'stack' - , offset = 'zero' - , order = 'default' - , interpolate = 'linear' // controls the line interpolation - , clipEdge = false // if true, masks lines within x and y scale - , x //can be accessed via chart.xScale() - , y //can be accessed via chart.yScale() - , scatter = nv.models.scatter() - , duration = 250 - , dispatch = d3.dispatch('areaClick', 'areaMouseover', 'areaMouseout','renderEnd', 'elementClick', 'elementMouseover', 'elementMouseout') - ; - - scatter - .pointSize(2.2) // default size - .pointDomain([2.2, 2.2]) // all the same size by default - ; - - /************************************ - * offset: - * 'wiggle' (stream) - * 'zero' (stacked) - * 'expand' (normalize to 100%) - * 'silhouette' (simple centered) - * - * order: - * 'inside-out' (stream) - * 'default' (input order) - ************************************/ - - var renderWatch = nv.utils.renderWatch(dispatch, duration); - - function chart(selection) { - renderWatch.reset(); - renderWatch.models(scatter); - selection.each(function(data) { - var availableWidth = width - margin.left - margin.right, - availableHeight = height - margin.top - margin.bottom; - - container = d3.select(this); - nv.utils.initSVG(container); - - // Setup Scales - x = scatter.xScale(); - y = scatter.yScale(); - - var dataRaw = data; - // Injecting point index into each point because d3.layout.stack().out does not give index - data.forEach(function(aseries, i) { - aseries.seriesIndex = i; - aseries.values = aseries.values.map(function(d, j) { - d.index = j; - d.seriesIndex = i; - return d; - }); - }); - - var dataFiltered = data.filter(function(series) { - return !series.disabled; - }); - - data = d3.layout.stack() - .order(order) - .offset(offset) - .values(function(d) { return d.values }) //TODO: make values customizeable in EVERY model in this fashion - .x(getX) - .y(getY) - .out(function(d, y0, y) { - d.display = { - y: y, - y0: y0 - }; - }) - (dataFiltered); - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea'); - var defsEnter = wrapEnter.append('defs'); - var gEnter = wrapEnter.append('g'); - var g = wrap.select('g'); - - gEnter.append('g').attr('class', 'nv-areaWrap'); - gEnter.append('g').attr('class', 'nv-scatterWrap'); - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - // If the user has not specified forceY, make sure 0 is included in the domain - // Otherwise, use user-specified values for forceY - if (scatter.forceY().length == 0) { - scatter.forceY().push(0); - } - - scatter - .width(availableWidth) - .height(availableHeight) - .x(getX) - .y(function(d) { return d.display.y + d.display.y0 }) - .forceY([0]) - .color(data.map(function(d,i) { - return d.color || color(d, d.seriesIndex); - })); - - var scatterWrap = g.select('.nv-scatterWrap') - .datum(data); - - scatterWrap.call(scatter); - - defsEnter.append('clipPath') - .attr('id', 'nv-edge-clip-' + id) - .append('rect'); - - wrap.select('#nv-edge-clip-' + id + ' rect') - .attr('width', availableWidth) - .attr('height', availableHeight); - - g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); - - var area = d3.svg.area() - .x(function(d,i) { return x(getX(d,i)) }) - .y0(function(d) { - return y(d.display.y0) - }) - .y1(function(d) { - return y(d.display.y + d.display.y0) - }) - .interpolate(interpolate); - - var zeroArea = d3.svg.area() - .x(function(d,i) { return x(getX(d,i)) }) - .y0(function(d) { return y(d.display.y0) }) - .y1(function(d) { return y(d.display.y0) }); - - var path = g.select('.nv-areaWrap').selectAll('path.nv-area') - .data(function(d) { return d }); - - path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i }) - .attr('d', function(d,i){ - return zeroArea(d.values, d.seriesIndex); - }) - .on('mouseover', function(d,i) { - d3.select(this).classed('hover', true); - dispatch.areaMouseover({ - point: d, - series: d.key, - pos: [d3.event.pageX, d3.event.pageY], - seriesIndex: d.seriesIndex - }); - }) - .on('mouseout', function(d,i) { - d3.select(this).classed('hover', false); - dispatch.areaMouseout({ - point: d, - series: d.key, - pos: [d3.event.pageX, d3.event.pageY], - seriesIndex: d.seriesIndex - }); - }) - .on('click', function(d,i) { - d3.select(this).classed('hover', false); - dispatch.areaClick({ - point: d, - series: d.key, - pos: [d3.event.pageX, d3.event.pageY], - seriesIndex: d.seriesIndex - }); - }); - - path.exit().remove(); - path.style('fill', function(d,i){ - return d.color || color(d, d.seriesIndex) - }) - .style('stroke', function(d,i){ return d.color || color(d, d.seriesIndex) }); - path.watchTransition(renderWatch,'stackedArea path') - .attr('d', function(d,i) { - return area(d.values,i) - }); - - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ - - scatter.dispatch.on('elementMouseover.area', function(e) { - g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true); - }); - scatter.dispatch.on('elementMouseout.area', function(e) { - g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false); - }); - - //Special offset functions - chart.d3_stackedOffset_stackPercent = function(stackData) { - var n = stackData.length, //How many series - m = stackData[0].length, //how many points per series - i, - j, - o, - y0 = []; - - for (j = 0; j < m; ++j) { //Looping through all points - for (i = 0, o = 0; i < dataRaw.length; i++) { //looping through all series - o += getY(dataRaw[i].values[j]); //total y value of all series at a certian point in time. - } - - if (o) for (i = 0; i < n; i++) { //(total y value of all series at point in time i) != 0 - stackData[i][j][1] /= o; - } else { //(total y value of all series at point in time i) == 0 - for (i = 0; i < n; i++) { - stackData[i][j][1] = 0; - } - } - } - for (j = 0; j < m; ++j) y0[j] = 0; - return y0; - }; - - }); - - renderWatch.renderEnd('stackedArea immediate'); - return chart; - } - - //============================================================ - // Global getters and setters - //------------------------------------------------------------ - - chart.dispatch = dispatch; - chart.scatter = scatter; - - scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); }); - scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); }); - scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); }); - - chart.interpolate = function(_) { - if (!arguments.length) return interpolate; - interpolate = _; - return chart; - }; - - chart.duration = function(_) { - if (!arguments.length) return duration; - duration = _; - renderWatch.reset(duration); - scatter.duration(duration); - return chart; - }; - - chart.dispatch = dispatch; - chart.scatter = scatter; - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, - offset: {get: function(){return offset;}, set: function(_){offset=_;}}, - order: {get: function(){return order;}, set: function(_){order=_;}}, - interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}}, - - // simple functor options - x: {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}}, - y: {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - }}, - style: {get: function(){return style;}, set: function(_){ - style = _; - switch (style) { - case 'stack': - chart.offset('zero'); - chart.order('default'); - break; - case 'stream': - chart.offset('wiggle'); - chart.order('inside-out'); - break; - case 'stream-center': - chart.offset('silhouette'); - chart.order('inside-out'); - break; - case 'expand': - chart.offset('expand'); - chart.order('default'); - break; - case 'stack_percent': - chart.offset(chart.d3_stackedOffset_stackPercent); - chart.order('default'); - break; - } - }}, - duration: {get: function(){return duration;}, set: function(_){ - duration = _; - renderWatch.reset(duration); - scatter.duration(duration); - }} - }); - - nv.utils.inheritOptions(chart, scatter); - nv.utils.initOptions(chart); - - return chart; - }; - - nv.models.stackedAreaChart = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var stacked = nv.models.stackedArea() - , xAxis = nv.models.axis() - , yAxis = nv.models.axis() - , legend = nv.models.legend() - , controls = nv.models.legend() - , interactiveLayer = nv.interactiveGuideline() - , tooltip = nv.models.tooltip() - ; - - var margin = {top: 30, right: 25, bottom: 50, left: 60} - , width = null - , height = null - , color = nv.utils.defaultColor() - , showControls = true - , showLegend = true - , showXAxis = true - , showYAxis = true - , rightAlignYAxis = false - , useInteractiveGuideline = false - , x //can be accessed via chart.xScale() - , y //can be accessed via chart.yScale() - , state = nv.utils.state() - , defaultState = null - , noData = null - , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd') - , controlWidth = 250 - , controlOptions = ['Stacked','Stream','Expanded'] - , controlLabels = {} - , duration = 250 - ; - - state.style = stacked.style(); - xAxis.orient('bottom').tickPadding(7); - yAxis.orient((rightAlignYAxis) ? 'right' : 'left'); - - tooltip - .headerFormatter(function(d, i) { - return xAxis.tickFormat()(d, i); - }) - .valueFormatter(function(d, i) { - return yAxis.tickFormat()(d, i); - }); - - interactiveLayer.tooltip - .headerFormatter(function(d, i) { - return xAxis.tickFormat()(d, i); - }) - .valueFormatter(function(d, i) { - return yAxis.tickFormat()(d, i); - }); - - var oldYTickFormat = null, - oldValueFormatter = null; - - controls.updateState(false); - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - var renderWatch = nv.utils.renderWatch(dispatch); - var style = stacked.style(); - - var stateGetter = function(data) { - return function(){ - return { - active: data.map(function(d) { return !d.disabled }), - style: stacked.style() - }; - } - }; - - var stateSetter = function(data) { - return function(state) { - if (state.style !== undefined) - style = state.style; - if (state.active !== undefined) - data.forEach(function(series,i) { - series.disabled = !state.active[i]; - }); - } - }; - - var percentFormatter = d3.format('%'); - - function chart(selection) { - renderWatch.reset(); - renderWatch.models(stacked); - if (showXAxis) renderWatch.models(xAxis); - if (showYAxis) renderWatch.models(yAxis); - - selection.each(function(data) { - var container = d3.select(this), - that = this; - nv.utils.initSVG(container); - - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); - - chart.update = function() { container.transition().duration(duration).call(chart); }; - chart.container = this; - - state - .setter(stateSetter(data), chart.update) - .getter(stateGetter(data)) - .update(); - - // DEPRECATED set state.disabled - state.disabled = data.map(function(d) { return !!d.disabled }); - - if (!defaultState) { - var key; - defaultState = {}; - for (key in state) { - if (state[key] instanceof Array) - defaultState[key] = state[key].slice(0); - else - defaultState[key] = state[key]; - } - } - - // Display No Data message if there's nothing to show. - if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { - nv.utils.noData(chart, container) - return chart; - } else { - container.selectAll('.nv-noData').remove(); - } - - // Setup Scales - x = stacked.xScale(); - y = stacked.yScale(); - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g'); - var g = wrap.select('g'); - - gEnter.append("rect").style("opacity",0); - gEnter.append('g').attr('class', 'nv-x nv-axis'); - gEnter.append('g').attr('class', 'nv-y nv-axis'); - gEnter.append('g').attr('class', 'nv-stackedWrap'); - gEnter.append('g').attr('class', 'nv-legendWrap'); - gEnter.append('g').attr('class', 'nv-controlsWrap'); - gEnter.append('g').attr('class', 'nv-interactive'); - - g.select("rect").attr("width",availableWidth).attr("height",availableHeight); - - // Legend - if (showLegend) { - var legendWidth = (showControls) ? availableWidth - controlWidth : availableWidth; - - legend.width(legendWidth); - g.select('.nv-legendWrap').datum(data).call(legend); - - if ( margin.top != legend.height()) { - margin.top = legend.height(); - availableHeight = nv.utils.availableHeight(height, container, margin); - } - - g.select('.nv-legendWrap') - .attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')'); - } - - // Controls - if (showControls) { - var controlsData = [ - { - key: controlLabels.stacked || 'Stacked', - metaKey: 'Stacked', - disabled: stacked.style() != 'stack', - style: 'stack' - }, - { - key: controlLabels.stream || 'Stream', - metaKey: 'Stream', - disabled: stacked.style() != 'stream', - style: 'stream' - }, - { - key: controlLabels.expanded || 'Expanded', - metaKey: 'Expanded', - disabled: stacked.style() != 'expand', - style: 'expand' - }, - { - key: controlLabels.stack_percent || 'Stack %', - metaKey: 'Stack_Percent', - disabled: stacked.style() != 'stack_percent', - style: 'stack_percent' - } - ]; - - controlWidth = (controlOptions.length/3) * 260; - controlsData = controlsData.filter(function(d) { - return controlOptions.indexOf(d.metaKey) !== -1; - }); - - controls - .width( controlWidth ) - .color(['#444', '#444', '#444']); - - g.select('.nv-controlsWrap') - .datum(controlsData) - .call(controls); - - if ( margin.top != Math.max(controls.height(), legend.height()) ) { - margin.top = Math.max(controls.height(), legend.height()); - availableHeight = nv.utils.availableHeight(height, container, margin); - } - - g.select('.nv-controlsWrap') - .attr('transform', 'translate(0,' + (-margin.top) +')'); - } - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - if (rightAlignYAxis) { - g.select(".nv-y.nv-axis") - .attr("transform", "translate(" + availableWidth + ",0)"); - } - - //Set up interactive layer - if (useInteractiveGuideline) { - interactiveLayer - .width(availableWidth) - .height(availableHeight) - .margin({left: margin.left, top: margin.top}) - .svgContainer(container) - .xScale(x); - wrap.select(".nv-interactive").call(interactiveLayer); - } - - stacked - .width(availableWidth) - .height(availableHeight); - - var stackedWrap = g.select('.nv-stackedWrap') - .datum(data); - - stackedWrap.transition().call(stacked); - - // Setup Axes - if (showXAxis) { - xAxis.scale(x) - ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) - .tickSize( -availableHeight, 0); - - g.select('.nv-x.nv-axis') - .attr('transform', 'translate(0,' + availableHeight + ')'); - - g.select('.nv-x.nv-axis') - .transition().duration(0) - .call(xAxis); - } - - if (showYAxis) { - var ticks; - if (stacked.offset() === 'wiggle') { - ticks = 0; - } - else { - ticks = nv.utils.calcTicksY(availableHeight/36, data); - } - yAxis.scale(y) - ._ticks(ticks) - .tickSize(-availableWidth, 0); - - if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') { - var currentFormat = yAxis.tickFormat(); - - if ( !oldYTickFormat || currentFormat !== percentFormatter ) - oldYTickFormat = currentFormat; - - //Forces the yAxis to use percentage in 'expand' mode. - yAxis.tickFormat(percentFormatter); - } - else { - if (oldYTickFormat) { - yAxis.tickFormat(oldYTickFormat); - oldYTickFormat = null; - } - } - - g.select('.nv-y.nv-axis') - .transition().duration(0) - .call(yAxis); - } - - //============================================================ - // Event Handling/Dispatching (in chart's scope) - //------------------------------------------------------------ - - stacked.dispatch.on('areaClick.toggle', function(e) { - if (data.filter(function(d) { return !d.disabled }).length === 1) - data.forEach(function(d) { - d.disabled = false; - }); - else - data.forEach(function(d,i) { - d.disabled = (i != e.seriesIndex); - }); - - state.disabled = data.map(function(d) { return !!d.disabled }); - dispatch.stateChange(state); - - chart.update(); - }); - - legend.dispatch.on('stateChange', function(newState) { - for (var key in newState) - state[key] = newState[key]; - dispatch.stateChange(state); - chart.update(); - }); - - controls.dispatch.on('legendClick', function(d,i) { - if (!d.disabled) return; - - controlsData = controlsData.map(function(s) { - s.disabled = true; - return s; - }); - d.disabled = false; - - stacked.style(d.style); - - - state.style = stacked.style(); - dispatch.stateChange(state); - - chart.update(); - }); - - interactiveLayer.dispatch.on('elementMousemove', function(e) { - stacked.clearHighlights(); - var singlePoint, pointIndex, pointXLocation, allData = []; - data - .filter(function(series, i) { - series.seriesIndex = i; - return !series.disabled; - }) - .forEach(function(series,i) { - pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x()); - var point = series.values[pointIndex]; - var pointYValue = chart.y()(point, pointIndex); - if (pointYValue != null) { - stacked.highlightPoint(i, pointIndex, true); - } - if (typeof point === 'undefined') return; - if (typeof singlePoint === 'undefined') singlePoint = point; - if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex)); - - //If we are in 'expand' mode, use the stacked percent value instead of raw value. - var tooltipValue = (stacked.style() == 'expand') ? point.display.y : chart.y()(point,pointIndex); - allData.push({ - key: series.key, - value: tooltipValue, - color: color(series,series.seriesIndex), - stackedValue: point.display - }); - }); - - allData.reverse(); - - //Highlight the tooltip entry based on which stack the mouse is closest to. - if (allData.length > 2) { - var yValue = chart.yScale().invert(e.mouseY); - var yDistMax = Infinity, indexToHighlight = null; - allData.forEach(function(series,i) { - - //To handle situation where the stacked area chart is negative, we need to use absolute values - //when checking if the mouse Y value is within the stack area. - yValue = Math.abs(yValue); - var stackedY0 = Math.abs(series.stackedValue.y0); - var stackedY = Math.abs(series.stackedValue.y); - if ( yValue >= stackedY0 && yValue <= (stackedY + stackedY0)) - { - indexToHighlight = i; - return; - } - }); - if (indexToHighlight != null) - allData[indexToHighlight].highlight = true; - } - - var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex)); - - var valueFormatter = interactiveLayer.tooltip.valueFormatter(); - // Keeps track of the tooltip valueFormatter if the chart changes to expanded view - if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') { - if ( !oldValueFormatter ) { - oldValueFormatter = valueFormatter; - } - //Forces the tooltip to use percentage in 'expand' mode. - valueFormatter = d3.format(".1%"); - } - else { - if (oldValueFormatter) { - valueFormatter = oldValueFormatter; - oldValueFormatter = null; - } - } - - interactiveLayer.tooltip - .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top}) - .chartContainer(that.parentNode) - .valueFormatter(valueFormatter) - .data( - { - value: xValue, - series: allData - } - )(); - - interactiveLayer.renderGuideLine(pointXLocation); - - }); - - interactiveLayer.dispatch.on("elementMouseout",function(e) { - stacked.clearHighlights(); - }); - - // Update chart from a state object passed to event handler - dispatch.on('changeState', function(e) { - - if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) { - data.forEach(function(series,i) { - series.disabled = e.disabled[i]; - }); - - state.disabled = e.disabled; - } - - if (typeof e.style !== 'undefined') { - stacked.style(e.style); - style = e.style; - } - - chart.update(); - }); - - }); - - renderWatch.renderEnd('stacked Area chart immediate'); - return chart; - } - - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ - - stacked.dispatch.on('elementMouseover.tooltip', function(evt) { - evt.point['x'] = stacked.x()(evt.point); - evt.point['y'] = stacked.y()(evt.point); - tooltip.data(evt).position(evt.pos).hidden(false); - }); - - stacked.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true) - }); - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - // expose chart's sub-components - chart.dispatch = dispatch; - chart.stacked = stacked; - chart.legend = legend; - chart.controls = controls; - chart.xAxis = xAxis; - chart.yAxis = yAxis; - chart.interactiveLayer = interactiveLayer; - chart.tooltip = tooltip; - - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, - showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, - showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, - defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, - noData: {get: function(){return noData;}, set: function(_){noData=_;}}, - showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}}, - controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}}, - controlOptions: {get: function(){return controlOptions;}, set: function(_){controlOptions=_;}}, - - // deprecated options - tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); - tooltip.enabled(!!_); - }}, - tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ - // deprecated after 1.7.1 - nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); - tooltip.contentGenerator(_); - }}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }}, - duration: {get: function(){return duration;}, set: function(_){ - duration = _; - renderWatch.reset(duration); - stacked.duration(duration); - xAxis.duration(duration); - yAxis.duration(duration); - }}, - color: {get: function(){return color;}, set: function(_){ - color = nv.utils.getColor(_); - legend.color(color); - stacked.color(color); - }}, - rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ - rightAlignYAxis = _; - yAxis.orient( rightAlignYAxis ? 'right' : 'left'); - }}, - useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){ - useInteractiveGuideline = !!_; - chart.interactive(!_); - chart.useVoronoi(!_); - stacked.scatter.interactive(!_); - }} - }); - - nv.utils.inheritOptions(chart, stacked); - nv.utils.initOptions(chart); - - return chart; - }; -// based on http://bl.ocks.org/kerryrodden/477c1bfb081b783f80ad - nv.models.sunburst = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var margin = {top: 0, right: 0, bottom: 0, left: 0} - , width = null - , height = null - , mode = "count" - , modes = {count: function(d) { return 1; }, size: function(d) { return d.size }} - , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one - , container = null - , color = nv.utils.defaultColor() - , duration = 500 - , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMousemove', 'elementMouseover', 'elementMouseout', 'renderEnd') - ; - - var x = d3.scale.linear().range([0, 2 * Math.PI]); - var y = d3.scale.sqrt(); - - var partition = d3.layout.partition() - .sort(null) - .value(function(d) { return 1; }); - - var arc = d3.svg.arc() - .startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); }) - .endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); }) - .innerRadius(function(d) { return Math.max(0, y(d.y)); }) - .outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); }); - - // Keep track of the current and previous node being displayed as the root. - var node, prevNode; - // Keep track of the root node - var rootNode; - - //============================================================ - // chart function - //------------------------------------------------------------ - - var renderWatch = nv.utils.renderWatch(dispatch); - - function chart(selection) { - renderWatch.reset(); - selection.each(function(data) { - container = d3.select(this); - var availableWidth = nv.utils.availableWidth(width, container, margin); - var availableHeight = nv.utils.availableHeight(height, container, margin); - var radius = Math.min(availableWidth, availableHeight) / 2; - var path; - - nv.utils.initSVG(container); - - // Setup containers and skeleton of chart - var wrap = container.selectAll('.nv-wrap.nv-sunburst').data(data); - var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sunburst nv-chart-' + id); - - var g = wrapEnter.selectAll('nv-sunburst'); - - wrap.attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')'); - - container.on('click', function (d, i) { - dispatch.chartClick({ - data: d, - index: i, - pos: d3.event, - id: id - }); - }); - - y.range([0, radius]); - - node = node || data; - rootNode = data[0]; - partition.value(modes[mode] || modes["count"]); - path = g.data(partition.nodes).enter() - .append("path") - .attr("d", arc) - .style("fill", function (d) { - return color((d.children ? d : d.parent).name); - }) - .style("stroke", "#FFF") - .on("click", function(d) { - if (prevNode !== node && node !== d) prevNode = node; - node = d; - path.transition() - .duration(duration) - .attrTween("d", arcTweenZoom(d)); - }) - .each(stash) - .on("dblclick", function(d) { - if (prevNode.parent == d) { - path.transition() - .duration(duration) - .attrTween("d", arcTweenZoom(rootNode)); - } - }) - .each(stash) - .on('mouseover', function(d,i){ - d3.select(this).classed('hover', true).style('opacity', 0.8); - dispatch.elementMouseover({ - data: d, - color: d3.select(this).style("fill") - }); - }) - .on('mouseout', function(d,i){ - d3.select(this).classed('hover', false).style('opacity', 1); - dispatch.elementMouseout({ - data: d - }); - }) - .on('mousemove', function(d,i){ - dispatch.elementMousemove({ - data: d - }); - }); - - - - // Setup for switching data: stash the old values for transition. - function stash(d) { - d.x0 = d.x; - d.dx0 = d.dx; - } - - // When switching data: interpolate the arcs in data space. - function arcTweenData(a, i) { - var oi = d3.interpolate({x: a.x0, dx: a.dx0}, a); - - function tween(t) { - var b = oi(t); - a.x0 = b.x; - a.dx0 = b.dx; - return arc(b); - } - - if (i == 0) { - // If we are on the first arc, adjust the x domain to match the root node - // at the current zoom level. (We only need to do this once.) - var xd = d3.interpolate(x.domain(), [node.x, node.x + node.dx]); - return function (t) { - x.domain(xd(t)); - return tween(t); - }; - } else { - return tween; - } - } - - // When zooming: interpolate the scales. - function arcTweenZoom(d) { - var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]), - yd = d3.interpolate(y.domain(), [d.y, 1]), - yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]); - return function (d, i) { - return i - ? function (t) { - return arc(d); - } - : function (t) { - x.domain(xd(t)); - y.domain(yd(t)).range(yr(t)); - return arc(d); - }; - }; - } - - }); - - renderWatch.renderEnd('sunburst immediate'); - return chart; - } - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - chart.dispatch = dispatch; - chart.options = nv.utils.optionsFunc.bind(chart); - - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - width: {get: function(){return width;}, set: function(_){width=_;}}, - height: {get: function(){return height;}, set: function(_){height=_;}}, - mode: {get: function(){return mode;}, set: function(_){mode=_;}}, - id: {get: function(){return id;}, set: function(_){id=_;}}, - duration: {get: function(){return duration;}, set: function(_){duration=_;}}, - - // options that require extra logic in the setter - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top != undefined ? _.top : margin.top; - margin.right = _.right != undefined ? _.right : margin.right; - margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom; - margin.left = _.left != undefined ? _.left : margin.left; - }}, - color: {get: function(){return color;}, set: function(_){ - color=nv.utils.getColor(_); - }} - }); - - nv.utils.initOptions(chart); - return chart; - }; - nv.models.sunburstChart = function() { - "use strict"; - - //============================================================ - // Public Variables with Default Settings - //------------------------------------------------------------ - - var sunburst = nv.models.sunburst(); - var tooltip = nv.models.tooltip(); - - var margin = {top: 30, right: 20, bottom: 20, left: 20} - , width = null - , height = null - , color = nv.utils.defaultColor() - , id = Math.round(Math.random() * 100000) - , defaultState = null - , noData = null - , duration = 250 - , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd') - ; - - //============================================================ - // Private Variables - //------------------------------------------------------------ - - var renderWatch = nv.utils.renderWatch(dispatch); - tooltip.headerEnabled(false).duration(0).valueFormatter(function(d, i) { - return d; - }); - - //============================================================ - // Chart function - //------------------------------------------------------------ - - function chart(selection) { - renderWatch.reset(); - renderWatch.models(sunburst); - - selection.each(function(data) { - var container = d3.select(this); - nv.utils.initSVG(container); - - var that = this; - var availableWidth = nv.utils.availableWidth(width, container, margin), - availableHeight = nv.utils.availableHeight(height, container, margin); - - chart.update = function() { - if (duration === 0) - container.call(chart); - else - container.transition().duration(duration).call(chart) - }; - chart.container = this; - - // Display No Data message if there's nothing to show. - if (!data || !data.length) { - nv.utils.noData(chart, container); - return chart; - } else { - container.selectAll('.nv-noData').remove(); - } - - // Setup containers and skeleton of chart - var wrap = container.selectAll('g.nv-wrap.nv-sunburstChart').data(data); - var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sunburstChart').append('g'); - var g = wrap.select('g'); - - gEnter.append('g').attr('class', 'nv-sunburstWrap'); - - wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - // Main Chart Component(s) - sunburst.width(availableWidth).height(availableHeight); - var sunWrap = g.select('.nv-sunburstWrap').datum(data); - d3.transition(sunWrap).call(sunburst); - - }); - - renderWatch.renderEnd('sunburstChart immediate'); - return chart; - } - - //============================================================ - // Event Handling/Dispatching (out of chart's scope) - //------------------------------------------------------------ - - sunburst.dispatch.on('elementMouseover.tooltip', function(evt) { - evt['series'] = { - key: evt.data.name, - value: evt.data.size, - color: evt.color - }; - tooltip.data(evt).hidden(false); - }); - - sunburst.dispatch.on('elementMouseout.tooltip', function(evt) { - tooltip.hidden(true); - }); - - sunburst.dispatch.on('elementMousemove.tooltip', function(evt) { - tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); - }); - - //============================================================ - // Expose Public Variables - //------------------------------------------------------------ - - // expose chart's sub-components - chart.dispatch = dispatch; - chart.sunburst = sunburst; - chart.tooltip = tooltip; - chart.options = nv.utils.optionsFunc.bind(chart); - - // use Object get/set functionality to map between vars and chart functions - chart._options = Object.create({}, { - // simple options, just get/set the necessary values - noData: {get: function(){return noData;}, set: function(_){noData=_;}}, - defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, - - // options that require extra logic in the setter - color: {get: function(){return color;}, set: function(_){ - color = _; - sunburst.color(color); - }}, - duration: {get: function(){return duration;}, set: function(_){ - duration = _; - renderWatch.reset(duration); - sunburst.duration(duration); - }}, - margin: {get: function(){return margin;}, set: function(_){ - margin.top = _.top !== undefined ? _.top : margin.top; - margin.right = _.right !== undefined ? _.right : margin.right; - margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; - margin.left = _.left !== undefined ? _.left : margin.left; - }} - }); - nv.utils.inheritOptions(chart, sunburst); - nv.utils.initOptions(chart); - return chart; - }; - - nv.version = "1.8.1"; -})(); \ No newline at end of file diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/stream_layers.js b/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/stream_layers.js deleted file mode 100644 index 43d7cfe0..00000000 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/units/analytics/public/js/stream_layers.js +++ /dev/null @@ -1,35 +0,0 @@ - -/* Inspired by Lee Byron's test data generator. */ -function stream_layers(n, m, o) { - if (arguments.length < 3) o = 0; - function bump(a) { - var x = 1 / (.1 + Math.random()), - y = 2 * Math.random() - .5, - z = 10 / (.1 + Math.random()); - for (var i = 0; i < m; i++) { - var w = (i / m - y) * z; - a[i] += x * Math.exp(-w * w); - } - } - return d3.range(n).map(function() { - var a = [], i; - for (i = 0; i < m; i++) a[i] = o + o * Math.random(); - for (i = 0; i < 5; i++) bump(a); - return a.map(stream_index); - }); -} - -/* Another layer generator using gamma distributions. */ -function stream_waves(n, m) { - return d3.range(n).map(function(i) { - return d3.range(m).map(function(j) { - var x = 20 * j / m - i / 3; - return 2 * x * Math.exp(-.5 * x); - }).map(stream_index); - }); -} - -function stream_index(d, i) { - return {x: i, y: Math.max(0, d)}; -} - diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/device-detail/device-detail.hbs b/modules/distribution/src/repository/jaggeryapps/iotserver/units/device-detail/device-detail.hbs index b261e3bc..234f0a13 100644 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/units/device-detail/device-detail.hbs +++ b/modules/distribution/src/repository/jaggeryapps/iotserver/units/device-detail/device-detail.hbs @@ -121,7 +121,7 @@ diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/device-listing/device-listing.hbs b/modules/distribution/src/repository/jaggeryapps/iotserver/units/device-listing/device-listing.hbs index e2633391..81f8b06f 100644 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/units/device-listing/device-listing.hbs +++ b/modules/distribution/src/repository/jaggeryapps/iotserver/units/device-listing/device-listing.hbs @@ -1,6 +1,6 @@ {{#zone "main"}} - +
    diff --git a/modules/distribution/src/repository/jaggeryapps/iotserver/units/group-detail/group-detail.hbs b/modules/distribution/src/repository/jaggeryapps/iotserver/units/group-detail/group-detail.hbs index 272963e1..ac63547d 100644 --- a/modules/distribution/src/repository/jaggeryapps/iotserver/units/group-detail/group-detail.hbs +++ b/modules/distribution/src/repository/jaggeryapps/iotserver/units/group-detail/group-detail.hbs @@ -69,7 +69,7 @@