diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000000000000000000000000000000000000..7e88b38d0398aea954515ce924a56ed905532f53 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,95 @@ +{ + "extends": "eslint:recommended", + "env": { + "browser": true + }, + "globals": { + "Drupal": true, + "drupalSettings": true, + "drupalTranslations": true, + "domready": true, + "jQuery": true, + "_": true, + "matchMedia": true, + "Backbone": true, + "Modernizr": true, + "CKEDITOR": true + }, + "rules": { + // Errors. + "array-bracket-spacing": [2, "never"], + "block-scoped-var": 2, + "brace-style": [2, "stroustrup", {"allowSingleLine": true}], + "comma-dangle": [2, "never"], + "comma-spacing": 2, + "comma-style": [2, "last"], + "computed-property-spacing": [2, "never"], + "curly": [2, "all"], + "eol-last": 2, + "eqeqeq": [2, "smart"], + "guard-for-in": 2, + "indent": [2, 2, {"SwitchCase": 1}], + "key-spacing": [2, {"beforeColon": false, "afterColon": true}], + "keyword-spacing": [2, {"before": true, "after": true}], + "linebreak-style": [2, "unix"], + "lines-around-comment": [2, {"beforeBlockComment": true, "afterBlockComment": false}], + "new-parens": 2, + "no-array-constructor": 2, + "no-caller": 2, + "no-catch-shadow": 2, + "no-eval": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-extra-parens": [2, "functions"], + "no-implied-eval": 2, + "no-iterator": 2, + "no-label-var": 2, + "no-labels": 2, + "no-lone-blocks": 2, + "no-loop-func": 2, + "no-multi-spaces": 2, + "no-multi-str": 2, + "no-native-reassign": 2, + "no-nested-ternary": 2, + "no-new-func": 2, + "no-new-object": 2, + "no-new-wrappers": 2, + "no-octal-escape": 2, + "no-process-exit": 2, + "no-proto": 2, + "no-return-assign": 2, + "no-script-url": 2, + "no-sequences": 2, + "no-shadow-restricted-names": 2, + "no-spaced-func": 2, + "no-trailing-spaces": 2, + "no-undef-init": 2, + "no-undefined": 2, + "no-unused-expressions": 2, + "no-unused-vars": [2, {"vars": "all", "args": "none"}], + "no-with": 2, + "object-curly-spacing": [2, "never"], + "one-var": [2, "never"], + "quote-props": [2, "consistent-as-needed"], + "quotes": [2, "single", "avoid-escape"], + "semi": [2, "always"], + "semi-spacing": [2, {"before": false, "after": true}], + "space-before-blocks": [2, "always"], + "space-before-function-paren": [2, {"anonymous": "always", "named": "never"}], + "space-in-parens": [2, "never"], + "space-infix-ops": 2, + "space-unary-ops": [2, { "words": true, "nonwords": false }], + "spaced-comment": [2, "always"], + "strict": 2, + "yoda": [2, "never"], + // Warnings. + "max-nested-callbacks": [1, 3], + "valid-jsdoc": [1, { + "prefer": { + "returns": "return", + "property": "prop" + }, + "requireReturn": false + }] + } +} diff --git a/d3_sankey.info b/d3_sankey.info index 9b7d1d237aa01e5f876469ba8b739400fed271e4..8c5dcbc42efb0471b3f8fea3c57f6c3c02a0028f 100644 --- a/d3_sankey.info +++ b/d3_sankey.info @@ -1 +1,7 @@ name = D3 Sankey +description = Creates sankey diagrams with D3 library. +core = 7.x +package = Visualization + +dependencies[] = libraries (>=2.0) +dependencies[] = d3 diff --git a/d3_sankey.install b/d3_sankey.install new file mode 100644 index 0000000000000000000000000000000000000000..1f5cb50392b172e7b023ebf0e046a96a0595981d --- /dev/null +++ b/d3_sankey.install @@ -0,0 +1,64 @@ + $t('d3-sankey')); + $library = libraries_detect('d3-plugins-sankey'); + if ($library['installed']) { + $requirements['d3_plugins_sankey']['severity'] = REQUIREMENT_OK; + $requirements['d3_plugins_sankey']['value'] = $library['version']; + } + else { + $requirements['d3_plugins_sankey']['severity'] = REQUIREMENT_ERROR; + $requirements['d3_plugins_sankey']['value'] = $library['error']; + $requirements['d3_plugins_sankey']['description'] = $library['error message']; + } + + // A requirements row for the d3.chart library. + $requirements['d3_charts'] = array('title' => $t('D3: Chart')); + $library = libraries_detect('d3.chart'); + if ($library['installed']) { + $requirements['d3_charts']['severity'] = REQUIREMENT_OK; + $requirements['d3_charts']['value'] = $library['version']; + } + else { + $requirements['d3_charts']['severity'] = REQUIREMENT_ERROR; + $requirements['d3_charts']['value'] = $library['error']; + $requirements['d3_charts']['description'] = $library['error message']; + } + + // A requirements row for the d3.chart.sankey library. + $requirements['d3_charts_sankey'] = array('title' => $t('D3: Sankey')); + $library = libraries_detect('d3.chart.sankey'); + if ($library['installed']) { + $requirements['d3_charts_sankey']['severity'] = REQUIREMENT_OK; + $requirements['d3_charts_sankey']['value'] = $library['version']; + } + else { + $requirements['d3_charts_sankey']['severity'] = REQUIREMENT_ERROR; + $requirements['d3_charts_sankey']['value'] = $library['error']; + $requirements['d3_charts_sankey']['description'] = $library['error message']; + } + } + + return $requirements; +} + +/** + * Installs d3_sankey module. + */ +function d3_sankey_update_7000(&$sandbox) { + // Noop to ensure Drupal stores a schema version in the database. +} diff --git a/d3_sankey.module b/d3_sankey.module new file mode 100644 index 0000000000000000000000000000000000000000..be49a1d4f66b188080b06bfa49769cf8ac1c52dd --- /dev/null +++ b/d3_sankey.module @@ -0,0 +1,202 @@ + 'd3-sankey', + 'vendor url' => 'https://github.com/newrelic-forks/d3-plugins-sankey', + 'download url' => 'https://github.com/newrelic-forks/d3-plugins-sankey/releases', + 'download file url' => 'https://github.com/newrelic-forks/d3-plugins-sankey/archive/v1.1.0.tar.gz', + 'version arguments' => array( + 'file' => 'bower.json', + 'pattern' => '@"version"[\s]*:[\s]*"([0-9a-zA-Z\.-]+)"@', + ), + 'files' => array( + 'js' => array('sankey.js'), + ), + 'dependencies' => array('d3'), + ); + + // The d3.chart library. + $libraries['d3.chart'] = array( + 'name' => 'd3.chart', + 'vendor url' => 'https://github.com/misoproject/d3.chart', + 'download url' => 'https://github.com/misoproject/d3.chart/releases', + 'download file url' => 'https://github.com/misoproject/d3.chart/archive/v0.2.1.tar.gz', + 'version arguments' => array( + 'file' => 'd3.chart.js', + 'pattern' => '@d3\.chart[\s]+-[\s]+v([0-9a-zA-Z\.-]+)@', + ), + 'files' => array( + 'js' => array('d3.chart.js'), + ), + 'variants' => array( + 'minified' => array( + 'js' => array('d3.chart.min.js'), + ), + ), + 'dependencies' => array('d3'), + ); + + // The d3.chart.sankey library. + $libraries['d3.chart.sankey'] = array( + 'name' => 'd3.chart.sankey', + 'vendor url' => 'https://github.com/q-m/d3.chart.sankey', + 'download url' => 'https://github.com/q-m/d3.chart.sankey/releases', + 'download file url' => 'https://github.com/q-m/d3.chart.sankey/archive/v0.2.0.tar.gz', + 'version arguments' => array( + 'file' => 'd3.chart.sankey.js', + 'pattern' => '@d3\.chart\.sankey[\s]+-[\s]+v([0-9a-zA-Z\.-]+)@', + ), + 'files' => array( + 'js' => array('d3.chart.sankey.js'), + ), + 'variants' => array( + 'minified' => array( + 'js' => array('d3.chart.sankey.min.js'), + ), + ), + 'dependencies' => array('d3-plugins-sankey', 'd3.chart'), + 'integration files' => array( + 'd3_charts_sankey' => array( + 'js' => array('d3_charts_sankey.js'), + ), + ), + ); + + return $libraries; +} + +/** + * Implements hook_libraries_info_file_paths(). + */ +function d3_sankey_libraries_info_file_paths() { + $paths = array(); + + // See the file doc comment for context. Since we are defining a dummy JS + // library that exists solely for the purpose of making this module work with + // the D3 Drupal module, and also because D3 expects us to include it in this + // module, we need to tell the Libraries API module where to find the library + // file. + $paths[] = _d3_sankey_js_library_path_prefix() . 'd3.sankey'; + + return $paths; +} + +/** + * Implements hook_libraries_info_alter(). + */ +function d3_sankey_libraries_info_alter(&$libraries) { + // See the file doc comment for context. By a convention set by the D3 Drupal + // module and also used by the d3_charts module (a separate project that also + // defines a plugin for the D3 Drupal module), we are putting the + // .libraries.info file that defines a library to the Libraries API inside the + // folder that contains the library. So we also have to tell the Libraries API + // where to look for the .libraries.info file. + $libraries['d3.sankey']['library path'] = _d3_sankey_js_library_path_prefix() + . 'd3.sankey'; +} + +/* Helper functions. */ + +/** + * Helper function to return the path to the libraries contained in this module. + * + * @param bool $reset + * Reset the static cache of the path. + * + * @return string + * A string representing the path to the libraries contained in this module. + */ +function _d3_sankey_js_library_path_prefix($reset = FALSE) { + $path = &drupal_static(__FUNCTION__); + if (!isset($path) || $reset) { + $path = drupal_get_path('module', 'd3_sankey') . '/libraries/'; + } + + return (string) $path; +} diff --git a/libraries/d3.sankey/d3.sankey.libraries.info b/libraries/d3.sankey/d3.sankey.libraries.info new file mode 100644 index 0000000000000000000000000000000000000000..a727bb3b889561f3b04efe8d361ee57e36596f7b --- /dev/null +++ b/libraries/d3.sankey/d3.sankey.libraries.info @@ -0,0 +1,7 @@ +name = Sankey +files[js][] = sankey.js +files[css][] = sankey.css +version = 0.1 +dependencies[] = d3-plugins-sankey +dependencies[] = d3.chart +dependencies[] = d3.chart.sankey diff --git a/libraries/d3.sankey/sankey.css b/libraries/d3.sankey/sankey.css new file mode 100644 index 0000000000000000000000000000000000000000..f2cb2cdc3b0a0c4e1e16d01e6a84cb1cab2fa595 --- /dev/null +++ b/libraries/d3.sankey/sankey.css @@ -0,0 +1,13 @@ +.sankey .node rect { + fill-opacity: .9; + shape-rendering: crispEdges; + stroke-width: 0; +} +.sankey .node text { + text-shadow: 0 1px 0 #fff; +} +.sankey .link { + fill: none; + stroke: #000; + stroke-opacity: .2; +} diff --git a/libraries/d3.sankey/sankey.js b/libraries/d3.sankey/sankey.js new file mode 100644 index 0000000000000000000000000000000000000000..feeac6a2813fb93e30e4c8a53d3d1b5e1de84b27 --- /dev/null +++ b/libraries/d3.sankey/sankey.js @@ -0,0 +1,47 @@ +/** + * @file + * D3 Sankey library. + */ + +(function ($, Drupal, d3) { + + 'use strict'; + + Drupal.d3.sankey = function (select, settings) { + var chartCanvas; + var chart; + + // Grab settings from the settings object passed to us by the D3 module. Use + // defaults if the settings we want do not exist in the settings object. + var sankeyType = (settings.sankeyType || 'Sankey'); + var width = (settings.width || 700); + var height = (settings.height || 400); + var nodeWidth = (settings.nodeWidth || 24); + var nodePadding = (settings.nodePadding || 8); + var spread = (settings.spread || true); + var iterations = (settings.iterations || 1); + var alignLabel = (settings.alignLabel || 'auto'); + + // Grab data for the chart if it exists in it's raw form; if not, use empty + // arrays for now. + var nodes = (settings.nodes || []); + var links = (settings.links || []); + + // In the chart element, add an SVG tag with the correct classes, width, and + // height; and inside that, a group for the chart itself. + chartCanvas = d3.select('#' + settings.id) + .append('svg').attr('class', 'sankey').attr('width', width).attr('height', height); + + // Set up the chart. + chart = chartCanvas.chart(sankeyType); + chart.nodeWidth(nodeWidth) + .nodePadding(nodePadding) + .iterations(iterations) + .spread(spread) + .alignLabel(alignLabel); + + // Draw a sankey chart with the data. + chart.draw({nodes: nodes, links: links}); + }; + +})(jQuery, Drupal, d3); diff --git a/modules/d3_sankey_examples/d3_sankey_examples.info b/modules/d3_sankey_examples/d3_sankey_examples.info new file mode 100644 index 0000000000000000000000000000000000000000..af0989ba1da45152bf4a164d1c1185d30ae882de --- /dev/null +++ b/modules/d3_sankey_examples/d3_sankey_examples.info @@ -0,0 +1,6 @@ +name = D3 Sankey Examples +description = Several functions that outline the different uses of the d3.sankey API. +core = 7.x +package = Visualization + +dependencies[] = d3_sankey diff --git a/modules/d3_sankey_examples/d3_sankey_examples.install b/modules/d3_sankey_examples/d3_sankey_examples.install new file mode 100644 index 0000000000000000000000000000000000000000..472236072e5c1347e3197eb6377815bcdd34e04f --- /dev/null +++ b/modules/d3_sankey_examples/d3_sankey_examples.install @@ -0,0 +1,34 @@ + $t('D3 Sankey Examples module')); + $requirements['d3_sankey_examples']['severity'] = REQUIREMENT_WARNING; + $requirements['d3_sankey_examples']['value'] = $t('Publicly-visible example page at @link_text!', array( + '@link_href' => url('d3/examples/sankey'), + '@link_text' => 'd3/examples/sankey', + )); + $requirements['d3_sankey_examples']['description'] = $t('The D3 Sankey Examples module is only intended to be used as a developer reference: you should disable it on any site that can be seen by the general public, because it defines an example page that has no access control!'); + } + + return $requirements; +} + +/** + * Installs d3_sankey_examples module. + */ +function d3_sankey_examples_update_7000(&$sandbox) { + // Noop to ensure Drupal stores a schema version in the database. +} diff --git a/modules/d3_sankey_examples/d3_sankey_examples.module b/modules/d3_sankey_examples/d3_sankey_examples.module new file mode 100644 index 0000000000000000000000000000000000000000..cd4b73d1a77fdcc6a6aeaefa1fcd3153086716fa --- /dev/null +++ b/modules/d3_sankey_examples/d3_sankey_examples.module @@ -0,0 +1,162 @@ + 'Sankey chart', + 'description' => 'Uses d3_sankey module to create a sample Sankey chart.', + 'access callback' => TRUE, + 'page callback' => '_d3_sankey_examples_sankey', + 'type' => MENU_LOCAL_TASK, + ); + + return $items; +} + +/* Menu callbacks. */ + +/** + * Menu callback: Uses d3_sankey module to create a sample Sankey chart. + */ +function _d3_sankey_examples_sankey() { + $chart = array( + 'type' => 'sankey', + + // This will be the HTML id attribute of the div that will contain the + // chart. + 'id' => 'visualization', + + // You can (optionally) specify the type of Sankey chart: + // - D3_SANKEY_SANKEYTYPE_DEFAULT will not "select" the nodes or link when a + // user hovers over them. + // - D3_SANKEY_SANKEYTYPE_SELECTION will "select" a node or link when a user + // hovers over it, but that selection will be limited only to only one + // node/link - the one being hovered. + // - D3_SANKEY_SANKEYTYPE_PATH will "select" a node or link, as well as any + // nodes or links connected to it when a user hovers over it. + 'sankeyType' => D3_SANKEY_SANKEYTYPE_PATH, + + // You can (optionally) specify the width and/or height of the chart in + // pixels. These numbers must be positive integers. If you do not specify a + // width, sankey.js will choose 700px. If you do not specify a height, + // sankey.js will choose 400px. + 'width' => '700', + 'height' => '400', + + // You can (optionally) specify the width and padding of nodes. These + // numbers must be positive integers. If you do not specify a nodeWidth, + // sankey.js will choose 24px. If you do not specify a padding, sankey.js + // will choose 8px. + 'nodeWidth' => 24, + 'nodePadding' => 8, + + // You can (optionally) choose whether or not to spread the nodes across the + // full height of the chart. You must specify a boolean. If you do not set + // this setting, sankey.js will assume that you want to do so, as that looks + // better. + // + // See https://github.com/q-m/d3.chart.sankey for more information. + 'spread' => TRUE, + + // You can (optionally) specify how many additional times the Sankey library + // should pass over the diagram to try to make things look better. You must + // specify zero, or positive integer. Note that higher numbers will make the + // chart take longer to render. If you do not set this setting, sankey.js + // will assume 1. + 'iterations' => 1, + + // You can (optionally) specify how labels will be attached to nodes: + // - D3_SANKEY_ALIGNLABEL_AUTO will align node labels automatically. You + // probably want this alignment unless you are prepared to write custom + // CSS to handle labels overflowing one side of the diagram and being + // clipped. + // - D3_SANKEY_ALIGNLABEL_START will align node labels to the start of paths + // connecting from them. In LTR languages, this setting will result in the + // labels for the right-most column of nodes overflowing on the right-hand + // side of the chart; default CSS causes these labels to be clipped (i.e.: + // not visible). + // - D3_SANKEY_ALIGNLABEL_END will align node labels to the end of paths + // connecting to them. In LTR languages, this setting will result in the + // labels for the left-most column of nodes overflowing on the left-hand + // side of the chart; default CSS causes these labels to be clipped (i.e.: + // not visible). + 'alignLabel' => D3_SANKEY_ALIGNLABEL_AUTO, + + // The nodes are the boxes that appear in the diagram. You can define each + // node as an associative PHP array (which will get turned into a JS + // object). The D3, d3-plugins-sankey, d3.chart, and d3.chart.sankey source + // code does not explicitly specify what keys are valid in node objects, but + // the following keys are known to work: + // - 'name' (optional): A label for the node. + // - 'id' (optional): An SVG ID attribute (similar to an HTML ID attribute) + // to add to the node. + // + // It is important to note that the Sankey charting library *only identifies + // nodes using their array index*: you cannot use the 'id' attribute to + // define links between nodes! I have explicitly set the array indicies in + // the example below, but you don't need to. + // + // Note also that the Sankey charting library arranges nodes on the page + // based on how they link together (see the 'links' key below); you do not + // get control over this! + // + // The order that you specify the nodes doesn't really affect the final + // layout, but does matter, because their index is what you use to define + // the links between them (see below). You will probably want to add nodes + // to the bottom of the array so that you don't have to re-calculate all the + // link sources/targets. + 'nodes' => array( + 0 => array('name' => 'Revenue', 'id' => 'revenue'), + 1 => array('name' => 'New work', 'id' => 'lob_new_work'), + 2 => array('name' => 'Service level agreements', 'id' => 'lob_sla'), + 3 => array('name' => 'Tech support', 'id' => 'lob_techsupport'), + 4 => array('name' => 'Client 1', 'id' => 'client_1'), + 5 => array('name' => 'Client 2', 'id' => 'client_2'), + ), + + // The links are the paths between the nodes. You can define each link as an + // associative PHP array (which will get turned into a JS object). The D3, + // d3-plugins-sankey, d3.chart, and d3.chart.sankey source code does not + // explicitly specify what keys are valid in link objects, but the following + // keys are known to work: + // - source (required): The array index of the node to connect from. + // - target (required): The array index of the node to connect to. + // - value (optional): A number representing how big the link is. If you do + // not specify a value, the actual size of the link will depend on how + // many other links are connected to the source and target nodes. You can + // specify any number (integer, float; positive or negative): the Sankey + // charting library will assume that the value of all links are all in the + // same unit (e.g.: dollars, percentage points, etc.). + // + // The order of the links do not matter. I have visually grouped the links + // with blank lines to illustrate each path from one side of the Sankey + // diagram to the other. + 'links' => array( + array('value' => 10000, 'source' => 0, 'target' => 1), + array('value' => 7000, 'source' => 1, 'target' => 4), + array('value' => 3000, 'source' => 1, 'target' => 5), + + array('value' => 15000, 'source' => 0, 'target' => 2), + array('value' => 7500, 'source' => 2, 'target' => 4), + array('value' => 7500, 'source' => 2, 'target' => 5), + + array('value' => 8000, 'source' => 0, 'target' => 3), + array('value' => 4000, 'source' => 3, 'target' => 4), + array('value' => 4000, 'source' => 3, 'target' => 5), + ), + ); + + return d3_draw($chart); +}