Skip to content

Instantly share code, notes, and snippets.

@Cuahchic
Last active September 25, 2018 09:19
Show Gist options
  • Save Cuahchic/5147487e9d38e1c15efa8c723c11f69f to your computer and use it in GitHub Desktop.
Save Cuahchic/5147487e9d38e1c15efa8c723c11f69f to your computer and use it in GitHub Desktop.
Updateable Zoomable Mouseover Sunburst
height: 800
license: gpl-3.0
scrolling: yes

This is an updateable, zoomable sunburst diagram made using D3.js.

Interactive Features

Mouseover any segment to see its ancestry displayed at the bottom of the image.

If you want to see a smaller area of the chart in greater detail, double click on a segment to zoom in. Double click the central circle to zoom out.

To freeze the mouseover (e.g. to take a screenshot) single click any segment to freeze. Single click any segment to unfreeze.

Design Process

Ever since I saw Kerry Rodden's Sequences sunburst I fell is love with its beauty and the way it easily conveyed information. But in addition to that, I loved the interactivity and the way the end user could get up close and personal with the data.

I felt like there must be ways to extend this visualisation, for example including Mike's Zoomable Sunburst whilst keeping the ability to mouseover. I also wanted the end user to be able to switch between multiple files on the same page.

So here is my very own labour of love, the Updateable Zoomable Mouseover Sunburst! Here you can see the size in bytes of the folder structure of various programs installed on my laptop. Hopefully this will be useful to someone.

How To See The Code

If you replace the bl.ocks.org in the URL with gist.github.com it will show you the underlying code. Alternatively, use your browsers development tools.

Acknowledgements

Thanks to Kerry Rodden for inspiring this work, Jon Sadka who finally helped me understand arcTween and of course the legend that is Mike Bostock for creating the wonderful D3.js tool we all love.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Updateable Zoomable Sunburst</title>
<script src="//d3js.org/d3.v4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tinycolor/1.4.1/tinycolor.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" type="text/css"
href="https://fonts.googleapis.com/css?family=Open+Sans:400,600">
<link rel="stylesheet" type="text/css" href="sunburst.css"/>
</head>
<body>
<div id="main">
<div id="selector">
<select id="fileSelect" onchange="loadData();">
<option value="postgresSQL.csv" selected>Postgres SQL 9.2</option>
<option value="python.csv">Python 3.6.5</option>
<option value="R.csv">R 3.5.0</option>
</select>
</div>
<div id="assetInfo">
Total Bytes Shown = <span id="totalCount"></span>
</div>
<div id="chart">
</div>
<div id="explanation" style="visibility: hidden;">
<span id="percentage"></span> of bytes can be found at the directory
</div>
<div id="sequence"></div>
</div>
<div id="sidebar">
<input type="checkbox" id="togglelegend"> Legend<br/>
<div id="legend" style="visibility: hidden;"></div>
</div>
<script type="text/javascript" src="sunburst.js"></script>
</body>
</html>
end 8701131
bin-end 77824
data-base-1-end 4
data-base-11997-end 4
data-base-12002-end 4
data-global-end 512
data-pg_clog-end 8192
data-end 61
data-pg_log-end 0
data-pg_multixact-members-end 8192
data-pg_multixact-offsets-end 8192
data-pg_notify-end 8192
data-pg_stat_tmp-end 101
data-pg_subtrans-end 8192
data-pg_xlog-end 16777216
doc-end 3794
doc-extension-end 2372
doc-postgresql-html-end 12114
include-end 87883
include-informix-esql-end 1435
include-internal-end 6531
include-internal-libpq-end 6511
include-libpq-end 633
include-libxml-end 3359
include-libxslt-end 2116
include-openssl-end 29238
include-server-access-end 9569
include-server-bootstrap-end 1579
include-server-end 2379
include-server-catalog-end 1954
include-server-commands-end 510
include-server-datatype-end 5631
include-server-executor-end 8597
include-server-foreign-end 2606
include-server-lib-end 5491
include-server-libpq-end 1300
include-server-mb-end 17251
include-server-nodes-end 1799
include-server-optimizer-end 1534
include-server-parser-end 752
include-server-port-end 13141
include-server-port-win32-arpa-end 66
include-server-port-win32-end 39
include-server-port-win32-netinet-end 67
include-server-port-win32-sys-end 44
include-server-port-win32_msvc-end 42
include-server-port-win32_msvc-sys-end 45
include-server-portability-end 4264
include-server-postmaster-end 487
include-server-regex-end 16149
include-server-replication-end 3036
include-server-rewrite-end 911
include-server-snowball-end 1691
include-server-snowball-libstemmer-end 313
include-server-storage-end 28675
include-server-tcop-end 1445
include-server-tsearch-dicts-end 3697
include-server-tsearch-end 7829
include-server-utils-end 3986
installer-end 5673816
installer-server-end 14336
lib-end 44544
pgAdmin III-docs-en_US-end 6522
pgAdmin III-docs-en_US-hints-end 794
pgAdmin III-docs-en_US-_images-end 53237
pgAdmin III-docs-en_US-_static-end 25246
pgAdmin III-i18n-af_ZA-end 69201
pgAdmin III-i18n-ca_ES-end 61505
pgAdmin III-i18n-cs_CZ-end 61913
pgAdmin III-i18n-de_DE-end 93108
pgAdmin III-i18n-es_ES-end 115539
pgAdmin III-i18n-fr_FR-end 117937
pgAdmin III-i18n-it_IT-end 113266
pgAdmin III-i18n-ja_JP-end 103855
pgAdmin III-i18n-ko_KR-end 420
pgAdmin III-i18n-lv_LV-end 11727
pgAdmin III-i18n-end 35984
pgAdmin III-i18n-pl_PL-end 91195
pgAdmin III-i18n-pt_PT-end 116649
pgAdmin III-i18n-ru_RU-end 85182
pgAdmin III-i18n-sr_RS-end 420
pgAdmin III-i18n-zh_CN-end 84902
pgAdmin III-i18n-zh_TW-end 87117
pgAdmin III-plugins.d-end 7405
pgAdmin III-end 1166
scripts-images-end 3638
scripts-end 2266
share-end 27431
share-extension-end 208
share-locale-cs-LC_MESSAGES-end 71099
share-locale-de-LC_MESSAGES-end 72042
share-locale-es-LC_MESSAGES-end 73151
share-locale-fr-LC_MESSAGES-end 76605
share-locale-it-LC_MESSAGES-end 73077
share-locale-ja-LC_MESSAGES-end 79750
share-locale-ko-LC_MESSAGES-end 8635
share-locale-nb-LC_MESSAGES-end 5811
share-locale-pl-LC_MESSAGES-end 71982
share-locale-pt_BR-LC_MESSAGES-end 72531
share-locale-ro-LC_MESSAGES-end 1067
share-locale-ru-LC_MESSAGES-end 90596
share-locale-sv-LC_MESSAGES-end 16940
share-locale-ta-LC_MESSAGES-end 9986
share-locale-tr-LC_MESSAGES-end 1063
share-locale-zh_CN-LC_MESSAGES-end 68697
share-locale-zh_TW-LC_MESSAGES-end 64422
share-timezone-Africa-end 1592
share-timezone-America-end 1980
share-timezone-America-Argentina-end 1109
share-timezone-America-Indiana-end 1787
share-timezone-America-Kentucky-end 2361
share-timezone-America-North_Dakota-end 2389
share-timezone-Antarctica-end 187
share-timezone-Arctic-end 2251
share-timezone-Asia-end 1199
share-timezone-Atlantic-end 170
share-timezone-Australia-end 2274
share-timezone-Brazil-end 630
share-timezone-Canada-end 2093
share-timezone-end 127
share-timezone-Chile-end 2242
share-timezone-Etc-end 127
share-timezone-Europe-end 1918
share-timezone-Indian-end 187
share-timezone-Mexico-end 1618
share-timezone-Pacific-end 183
share-timezone-US-end 196
share-timezonesets-end 3687
share-tsearch_data-end 139
StackBuilder-share-i18n-de_DE-end 93394
StackBuilder-share-i18n-fr_FR-end 119063
StackBuilder-share-i18n-ja_JP-end 104193
StackBuilder-share-i18n-ru_RU-end 85441
StackBuilder-share-i18n-sv_SE-end 110221
StackBuilder-share-i18n-tr_TR-end 87041
StackBuilder-share-i18n-zh_CN-end 84902
symbols-end 338944
DLLs-end 69784
Doc-end 8065193
docx_template-docProps-end 45868
docx_template-word-end 6559
docx_template-word-theme-end 7559
docx_template-_rels-end 768
etc-jupyter-nbconfig-notebook.d-end 66
end 87888
include-end 2952
include-python_igraph-end 2652
libs-end 1750
man-man1-end 6076
Scripts-end 102791
Scripts-__pycache__-end 11780
selenium-webdriver-firefox-amd64-end 41262
selenium-webdriver-firefox-x86-end 30887
selenium-webdriver-remote-end 44013
share-applications-end 322
share-doc-networkx_2.1-examples-3d_drawing-end 1148
share-doc-networkx_2.1-examples-advanced-end 2872
share-doc-networkx_2.1-examples-algorithms-end 969
share-doc-networkx_2.1-examples-basic-end 867
share-doc-networkx_2.1-examples-drawing-end 1709
share-doc-networkx_2.1-examples-graph-end 33695
share-doc-networkx_2.1-examples-javascript-end 1150
share-doc-networkx_2.1-examples-jit-end 835
share-doc-networkx_2.1-examples-pygraphviz-end 1267
share-doc-networkx_2.1-examples-subclass-end 2650
share-doc-networkx_2.1-end 53
share-icons-end 5610
share-jupyter-kernels-python3-end 2180
share-jupyter-nbextensions-plotlywidget-end 8639018
share-man-man1-end 1039
share-metainfo-end 1481
tcl-dde1.4-end 35840
tcl-reg1.3-end 30208
tcl-tcl8-8.4-platform-end 5977
tcl-tcl8-8.4-end 10012
tcl-tcl8-8.5-end 100382
tcl-tcl8-8.6-end 42751
tcl-tcl8.6-end 4860
tcl-tcl8.6-encoding-end 1091
tcl-tcl8.6-http1.0-end 735
tcl-tcl8.6-msgs-end 346
tcl-tcl8.6-opt0.4-end 607
tcl-tcl8.6-tzdata-Africa-end 6288
tcl-tcl8.6-tzdata-America-end 7485
tcl-tcl8.6-tzdata-America-Argentina-end 2036
tcl-tcl8.6-tzdata-America-Indiana-end 7170
tcl-tcl8.6-tzdata-America-Kentucky-end 8279
tcl-tcl8.6-tzdata-America-North_Dakota-end 8281
tcl-tcl8.6-tzdata-Antarctica-end 145
tcl-tcl8.6-tzdata-Arctic-end 176
tcl-tcl8.6-tzdata-Asia-end 2013
tcl-tcl8.6-tzdata-Atlantic-end 184
tcl-tcl8.6-tzdata-Australia-end 207
tcl-tcl8.6-tzdata-Brazil-end 177
tcl-tcl8.6-tzdata-Canada-end 190
tcl-tcl8.6-tzdata-end 149
tcl-tcl8.6-tzdata-Chile-end 184
tcl-tcl8.6-tzdata-Etc-end 153
tcl-tcl8.6-tzdata-Europe-end 7055
tcl-tcl8.6-tzdata-Indian-end 146
tcl-tcl8.6-tzdata-Mexico-end 195
tcl-tcl8.6-tzdata-Pacific-end 174
tcl-tcl8.6-tzdata-SystemV-end 193
tcl-tcl8.6-tzdata-US-end 183
tcl-end 27960
tcl-tix8.4.3-end 1005
tcl-tix8.4.3-bitmaps-end 1262
tcl-tix8.4.3-demos-bitmaps-end 299
tcl-tix8.4.3-demos-end 13311
tcl-tix8.4.3-demos-samples-end 3557
tcl-tix8.4.3-pref-end 4466
tcl-tk8.6-end 26031
tcl-tk8.6-demos-end 23264
tcl-tk8.6-demos-images-end 196623
tcl-tk8.6-images-end 5473
tcl-tk8.6-msgs-end 3832
tcl-tk8.6-ttk-end 1920
Tools-demo-end 1526
Tools-i18n-end 22076
Tools-parser-end 20844
Tools-pynche-end 48
Tools-pynche-X-end 1381
Tools-scripts-end 1716
bin-end 88576
bin-i386-end 87054
bin-x64-end 88576
end 1202385
doc-end 3532
doc-html-end 1021
doc-manual-end 495388
doc-manual-images-end 7185
etc-end 589
etc-i386-end 6776
etc-x64-end 6820
include-end 2593
include-R_ext-end 1470
modules-i386-end 56846
modules-x64-end 58368
share-dictionaries-end 4222
share-encodings-end 53369
share-licenses-end 1380
share-make-end 1142
share-R-end 131
share-Rd-macros-end 2074
share-texmf-bibtex-bib-end 72374
share-texmf-bibtex-bst-end 32350
share-texmf-tex-latex-end 299
share-zoneinfo-Africa-end 1030
share-zoneinfo-America-end 1980
share-zoneinfo-America-Argentina-end 1095
share-zoneinfo-America-Indiana-end 1787
share-zoneinfo-America-Kentucky-end 2361
share-zoneinfo-America-North_Dakota-end 2389
share-zoneinfo-Antarctica-end 173
share-zoneinfo-Arctic-end 2251
share-zoneinfo-Asia-end 1199
share-zoneinfo-Atlantic-end 170
share-zoneinfo-Australia-end 2274
share-zoneinfo-Brazil-end 616
share-zoneinfo-Canada-end 2093
share-zoneinfo-end 127
share-zoneinfo-Chile-end 2228
share-zoneinfo-Etc-end 127
share-zoneinfo-Europe-end 1918
share-zoneinfo-Indian-end 173
share-zoneinfo-Mexico-end 1618
share-zoneinfo-Pacific-end 152
share-zoneinfo-US-end 196
src-library-windlgs-end 37
src-library-windlgs-man-end 1568
src-library-windlgs-R-end 1526
src-library-windlgs-src-end 5313
Tcl-bin-end 107520
Tcl-bin64-end 112640
Tcl-include-end 5565
Tcl-include-X11-end 20202
Tcl-lib-BWidget-end 5367
Tcl-lib-BWidget-BWman-end 14916
Tcl-lib-BWidget-demo-end 173640
Tcl-lib-BWidget-images-end 254
Tcl-lib-BWidget-lang-end 1673
Tcl-lib-BWidget-tests-end 6110
Tcl-lib-dde1.4-end 66667
Tcl-lib-itcl4.0.3-end 254
Tcl-lib-reg1.3-end 58260
Tcl-lib-sqlite3.8.8.3-end 898945
Tcl-lib-tcl8-8.4-platform-end 5977
Tcl-lib-tcl8-8.4-end 9890
Tcl-lib-tcl8-8.5-end 99905
Tcl-lib-tcl8-8.6-end 41999
Tcl-lib-tcl8-8.6-tdbc-end 19318
Tcl-lib-tcl8.6-end 4674
Tcl-lib-tcl8.6-encoding-end 1091
Tcl-lib-tcl8.6-http1.0-end 735
Tcl-lib-tcl8.6-msgs-end 346
Tcl-lib-tcl8.6-opt0.4-end 607
Tcl-lib-tcl8.6-tzdata-Africa-end 6288
Tcl-lib-tcl8.6-tzdata-America-end 7485
Tcl-lib-tcl8.6-tzdata-America-Argentina-end 2036
Tcl-lib-tcl8.6-tzdata-America-Indiana-end 7170
Tcl-lib-tcl8.6-tzdata-America-Kentucky-end 8279
Tcl-lib-tcl8.6-tzdata-America-North_Dakota-end 8281
Tcl-lib-tcl8.6-tzdata-Antarctica-end 145
Tcl-lib-tcl8.6-tzdata-Arctic-end 176
Tcl-lib-tcl8.6-tzdata-Asia-end 2013
Tcl-lib-tcl8.6-tzdata-Atlantic-end 184
Tcl-lib-tcl8.6-tzdata-Australia-end 207
Tcl-lib-tcl8.6-tzdata-Brazil-end 177
Tcl-lib-tcl8.6-tzdata-Canada-end 190
Tcl-lib-tcl8.6-tzdata-end 149
Tcl-lib-tcl8.6-tzdata-Chile-end 184
Tcl-lib-tcl8.6-tzdata-Etc-end 153
Tcl-lib-tcl8.6-tzdata-Europe-end 7055
Tcl-lib-tcl8.6-tzdata-Indian-end 146
Tcl-lib-tcl8.6-tzdata-Mexico-end 195
Tcl-lib-tcl8.6-tzdata-Pacific-end 174
Tcl-lib-tcl8.6-tzdata-SystemV-end 193
Tcl-lib-tcl8.6-tzdata-US-end 183
Tcl-lib-end 3493
Tcl-lib-tdbc1.0.3-end 3487
Tcl-lib-tdbcmysql1.0.3-end 76218
Tcl-lib-tdbcodbc1.0.3-end 88281
Tcl-lib-tdbcpostgres1.0.3-end 74753
Tcl-lib-thread2.7.2-end 29546
Tcl-lib-tk8.6-end 26031
Tcl-lib-tk8.6-demos-end 23261
Tcl-lib-tk8.6-demos-images-end 196623
Tcl-lib-tk8.6-images-end 5473
Tcl-lib-tk8.6-msgs-end 3832
Tcl-lib-tk8.6-ttk-end 1920
Tcl-lib-Tktable2.11-html-end 66891
Tcl-lib-Tktable2.11-end 180255
Tcl-lib64-dde1.4-end 68029
Tcl-lib64-itcl4.0.3-end 254
Tcl-lib64-reg1.3-end 61447
Tcl-lib64-sqlite3.8.8.3-end 844935
Tcl-lib64-tcl8-8.6-tdbc-end 19318
Tcl-lib64-end 3519
Tcl-lib64-tdbc1.0.3-end 3513
Tcl-lib64-tdbcmysql1.0.3-end 78858
Tcl-lib64-tdbcodbc1.0.3-end 89673
Tcl-lib64-tdbcpostgres1.0.3-end 78071
Tcl-lib64-thread2.7.2-end 29546
Tcl-lib64-tk8.6-end 392
Tcl-lib64-Tktable2.11-html-end 66891
Tcl-lib64-Tktable2.11-end 178278
tests-end 42
tests-Examples-end 30485
#explanation {
font-family: 'Open Sans', sans-serif;
font-size: 12px;
font-weight: 400;
background-color: #fff;
width: 500px;
height: 40px;
line-height: 40px;
margin-top: 10px;
display: block;
color: #666;
z-index: -1;
text-align: center;
vertical-align: middle;
border-style: solid;
border-width: 5px;
border-radius: 20px;
border-color: lightgrey;
margin-left: 125px;
margin-bottom: 10px;
-webkit-user-select: none; /* webkit (safari, chrome) browsers */
-moz-user-select: none; /* mozilla browsers */
-ms-user-select: none; /* IE10+ */
}
#main {
float: left;
width: 750px;
}
#sidebar {
float: right;
width: 500px;
}
#selector {
width: 750px;
height: 50px;
}
#sequence {
width: 750px;
height: 70px;
font-family: 'Open Sans', sans-serif;
font-size: 12px;
font-weight: 400;
background-color: #fff;
-webkit-user-select: none; /* webkit (safari, chrome) browsers */
-moz-user-select: none; /* mozilla browsers */
-ms-user-select: none; /* IE10+ */
}
#assetInfo {
width: 750px;
height: 40px;
font-family: 'Open Sans', sans-serif;
font-size: 24px;
font-weight: 600;
background-color: #fff;
line-height: 30px;
text-align: center;
vertical-align: middle;
}
#legend {
padding: 10px 0 0 3px;
font-size: 11px;
}
#sequence text, #legend text {
font-weight: 600;
fill: #fff;
}
#chart {
position: relative;
}
#chart path {
stroke: #fff;
}
#percentage {
font-size: 1.5em;
}
// ***** MAIN CODE *****
// Basic dimensions
var width = 750;
var height = 600;
var radius = Math.min(width, height) / 2;
var radius2 = radius * radius;
// Stashed dictionaries of values
var colors = {};
var jsonAlarms = {};
var jsonFiles = {};
// Cached selected and status values
var filesel;
var timesel;
var lastFileViewed;
var action = 1; // 1 = we are on the first load, 0 = chart is already loaded and we are only updating
var lockStatus = 0; // 1 = view is locked on a specific level, 0 = view is not locked and it is possible to mouseover and zoom
var dblclickDuration = 600;
var updating = 0; // 1 = in the middle of updating a transition so disable mouseover, 0 = no transition ongoing
var tweenActive = 0;
// Get x and y for zooming purposes
var x = d3.scaleLinear()
.range([0, 2 * Math.PI]);
var y = d3.scaleSqrt()
.range([0, radius]);
// Breadcrumb dimensions: width, height, spacing, width of tip/tail.
var b = {
w: width, h: 30, s: 3, t: 8
};
// Dimensions of legend item: width, height, spacing, radius of rounded rect.
var li = {
w: 500, h: 24, s: 3, r: 3
};
// Save delimiter as variable
var delim = "::";
// Total size of all segments; we set this later, after loading the data.
var totalSize = 0;
var vis = d3.select("#chart").append("svg:svg")
.attr("width", width)
.attr("height", height)
.append("svg:g")
.attr("id", "container")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var partition = d3.partition();
var arc = d3.arc()
.startAngle(function (d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x0))); })
.endAngle(function (d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x1))); })
.innerRadius(function (d) { return Math.max(0, y(d.y0)); })
.outerRadius(function (d) { return Math.max(0, y(d.y1)); });
filesel = $("#fileSelect");
loadData();
// ***** MAIN CODE END *****
// Read data from disk and create sunburst
function loadData() {
if (updating == 0) {
var csvFileName = filesel[0].value;
d3.text(csvFileName, function(text) {
var csv = d3.csvParseRows(text);
var json = buildHierarchy(csv);
loadSunburst(json);
});
}
}
// Load the sunburst
function loadSunburst(json) {
// Get unique alarms and create colours for them
var uniqueKeys = findUniqueKeys(json["children"], "name");
generateColours(uniqueKeys);
if (action == 1) {
createVisualization(json); // Load everything for first time
drawLegend();
} else {
totalSize = updateVisualisation(json);
updateLegend();
}
d3.select("#totalCount").text(totalSize);
}
// Function to randomise array, from http://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex;
// While there remain elements to shuffle...
while (0 !== currentIndex) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
// And swap it with the current element.
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
// Function to get unique keys so we can assign them an appropriate colour
function findUniqueKeys(json, attr) {
var allKeys = [];
var uniqueKeys = [];
var wholeString = "";
if (typeof(json) != "undefined") {
for (var i = 0; i < json.length; i++) {
allKeys.push(json[i][attr]); // Push current attribute into array
var subspace = json[i]["children"]; // Get child elements
var newKeys = findUniqueKeys(subspace, attr);
allKeys = allKeys.concat(newKeys); // Recursively call function until leaf node reached
}
allKeys.sort();
for (var i = 0; i < allKeys.length - 1; i++) {
if (allKeys[i + 1] != allKeys[i]) { // If current element does not equal next element then add it
uniqueKeys.push(allKeys[i]);
}
}
uniqueKeys.push(allKeys[allKeys.length - 1]); // Last element doesn't have next element to distinguish it, so always add last
}
return uniqueKeys;
}
// Create colour palette
function generateColours(uniqueKeys) {
var hueStep = Math.max(Math.floor(360 / uniqueKeys.length), 1);
var hexStrings = [];
for (k in colors) { // Deactivate all colours for legend plotting
colors[k].active = 0;
}
for (var i = 0; i < uniqueKeys.length; i++) {
var H = (i * hueStep) % 360;
var S = Math.floor(Math.random() * 50) + 30; // Gives range between 30 and 80
var L = Math.floor(Math.random() * 45) + 35; // Gives range between 35 and 80
var hslValue = "hsl(" + H + ", " + S + "%, " + L + "%)";
var hslValueTC = tinycolor(hslValue);
hexStrings.push(hslValueTC.toHexString());
}
hexStrings = shuffle(hexStrings); // Randomise array for assignment
for (var i = 0; i < uniqueKeys.length; i++) {
if (!(uniqueKeys[i] in colors)) { // Only update colours array if we haven't seen this alarm before
colors[uniqueKeys[i]] = { "fill": hexStrings[i], "active": 0 };
}
colors[uniqueKeys[i]].active = 1;
}
colors["end"] = "#000000"
}
// Main function to draw and set up the visualization, once we have the data.
function createVisualization(json) {
// Basic setup of page elements.
initializeBreadcrumbTrail();
d3.select("#togglelegend").on("click", toggleLegend);
// Bounding circle underneath the sunburst, to make it easier to detect
// when the mouse leaves the parent g.
vis.append("svg:circle")
.attr("r", radius)
.attr("id", "boundingCircle")
.style("opacity", 0);
// Get total size of the tree = value of root node from partition.
totalSize = updateVisualisation(json);
// Add the mouseleave handler to the bounding circle.
d3.select("#container").on("mouseleave", mouseleave);
};
// Function for updating chart when a new dataset is chosen
function updateVisualisation(json) {
// Emulate double click event on root node so that new chart will start zoomed all the way out
if (tweenActive == 1) {
d3.select("#rootNode").dispatch("dblclick");
}
// For efficiency, filter nodes to keep only those large enough to see.
var h = d3.hierarchy(json);
h
.sum(function (d) { return d.size; }) // Determine how to list value of children
.sort(function (a, b) { return b.value - a.value; }) // Descending order sorting
var nodes = partition(h).descendants();
// Wait for the double click duration in case an animation is ongoing
setTimeout(function () {
updating = 1;
if (action == 1) {
createPaths(nodes);
} else {
updatePaths(nodes);
}
vis.selectAll("path")
.on("mouseover", mouseover)
.on("click", click)
.on("dblclick", dblclick);
updating = 0;
// We've loaded for the first time so reset action to 0
action = 0;
}
, dblclickDuration + 10, nodes);
return h.value;
}
// Function to create path elements
function createPaths(nodes) {
var path = vis.selectAll("path")
.data(nodes)
path.enter().append("svg:path") // This adds new path elements to sunburst for new data points
.merge(path) // Merges back with original data
.transition().duration(250)
.attr("d", arc)
.attr("fill-rule", "evenodd")
.attr("id", function (d) { return d.depth ? null : "rootNode" })
.style("fill", function (d) { return d.depth ? colors[d.data.name].fill : "white"; })
.style("opacity", 1)
.each(function(d) {
this.__data__._current = d;
}); // Temporarily save the current values of d for transitioning later
path.exit().remove(); // This removes unused data points from the chart
}
// Function to update chart elements
function updatePaths(nodes) {
var path = vis.selectAll("path")
.data(nodes)
path.enter().append("svg:path") // This adds new path elements to sunburst for new data points
.merge(path) // Merges back with original data
.each(function(d) {
this.__data__._current = d;
}) // Temporarily save the current values of d for transitioning later
.transition().duration(250)
.attrTween("d", arcTween)
.attr("fill-rule", "evenodd")
.attr("id", function (d) { return d.depth ? null : "rootNode" })
.style("fill", function (d) { return d.depth ? colors[d.data.name].fill : "white"; })
.style("opacity", 1);
path.exit().remove(); // This removes unused data points from the chart
}
// Function to calculate the tween for transitions
function arcTween(d) {
// Need to create custom dictionaries as there are arrays with children in __data__, d3 will try and interpolate these and blow the stack!
var old_vals = {'x0': this.__data__._current.x0,
'x1': this.__data__._current.x1,
'y0': this.__data__._current.y0,
'y1': this.__data__._current.y1}
var new_vals = {'x0': d.x0,
'x1': d.x1,
'y0': d.y0,
'y1': d.y1}
var i = d3.interpolate(old_vals, new_vals);
this.__data__._current = i(0);
return function(t) {
return arc(i(t));
};
}
// Fade all but the current sequence, and show it in the breadcrumb trail.
function mouseover(d) {
if (updating == 0) {
var percentage = (100 * d.value / totalSize).toPrecision(3);
var percentageString = d.value + " (" + percentage + "%)";
if (percentage < 0.1) {
percentageString = d.value + " (< 0.1%)";
}
d3.select("#percentage")
.text(percentageString);
d3.select("#explanation")
.style("visibility", "");
var sequenceArray = d.ancestors().sort(function (a, b) { return a.depth - b.depth; });
sequenceArray.shift();
updateBreadcrumbs(sequenceArray, percentageString);
// Fade all the segments.
d3.selectAll("path")
.style("opacity", 0.10);
// Then highlight only those that are an ancestor of the current segment.
vis.selectAll("path")
.filter(function (node) {
return (sequenceArray.indexOf(node) >= 0);
})
.style("opacity", 1);
}
}
// Restore everything to full opacity when moving off the visualization.
function mouseleave(d) {
if (updating == 0) {
// Hide the breadcrumb trail
d3.select("#trail")
.style("visibility", "hidden");
var path = d3.selectAll("path");
// Deactivate all segments during transition.
path.on("mouseover", null);
// Transition each segment to full opacity and then reactivate it.
path
.transition("brighten")
.duration(250)
.style("opacity", 1)
.on("end", function () {
d3.select(this).on("mouseover", mouseover);
});
d3.select("#explanation")
.style("visibility", "hidden");
}
}
// Given a node in a partition layout, return an array of all of its ancestor
// nodes, highest first, but excluding the root.
function getAncestors(node) {
var path = [];
var current = node;
while (current.parent) {
path.unshift(current);
current = current.parent;
}
return path;
}
function initializeBreadcrumbTrail() {
// Add the svg area.
var trail = d3.select("#sequence").append("svg:svg")
.attr("width", width)
.attr("height", height * 2)
.attr("id", "trail");
}
// Generate a string that describes the points of a breadcrumb polygon.
function breadcrumbPoints(d, i) {
var addonThickness = 0;
if (!("children" in d)) { // Very last breadcrumb, show breadcrumb shape as flat at the bottom
addonThickness = b.t;
}
var points = [];
points.push(b.w + ",0");
points.push(b.w + "," + (b.h + addonThickness));
points.push((b.w / 2) + "," + (b.h + b.t));
points.push("0," + (b.h + addonThickness));
points.push("0,0");
if (i > 0) { // Topmost breadcrumb; don't include 6th vertex.
points.push((b.w / 2) + "," + b.t);
}
return points.join(" ");
}
// Update the breadcrumb trail to show the current sequence and percentage.
function updateBreadcrumbs(nodeArray, percentageString) {
// Data join; key function combines name and depth (= position in sequence).
var g = d3.select("#trail")
.selectAll("g")
.data(nodeArray, function(d) { return d.data.name + d.depth; });
// Add breadcrumb and label for entering nodes.
var entering = g.enter().append("svg:g");
entering.append("svg:polygon")
.attr("points", breadcrumbPoints)
.style("fill", function(d) { return colors[d.data.name].fill; });
entering.append("svg:text")
.attr("x", (b.w / 2))
.attr("y", ((b.h + b.s) / 2))
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.data.name; });
// Remove exiting nodes.
g.exit().remove();
// Set position for entering and updating nodes.
entering.attr("transform", function(d, i) {
return "translate(0," + i * (b.h + b.s) + ")";
});
// Make the breadcrumb trail visible, if it's hidden.
d3.select("#trail")
.style("visibility", "");
}
// Required during initial set up of legend
function drawLegend() {
var legend = d3.select("#legend").append("svg:svg")
.attr("width", li.w)
.attr("height", d3.keys(colors).length * (li.h + li.s));
updateLegend();
}
function updateLegend() {
var legend = d3.select("#legend")
var svg = legend.select("svg")
.attr("height", d3.keys(colors).length * (li.h + li.s)); // Resize legend svg box to new size of colours array
svg.selectAll("g").remove();
// Order the colours so the legend appears in alphabetical order
var orderedColors = {};
Object.keys(colors).sort().forEach(function (key) {
orderedColors[key] = colors[key];
});
var g = svg.selectAll("g").data(d3.entries(orderedColors), function (d) { return d.key; })
// Append new legend elements
var entering = g.enter().append("svg:g")
.filter(function(d) { return d.value.active == 1; })
.attr("transform", function(d, i) {
return "translate(0," + i * (li.h + li.s) + ")";
});
// Add new legend boxes
entering.append("svg:rect")
.attr("rx", li.r)
.attr("ry", li.r)
.attr("width", li.w)
.attr("height", li.h)
.style("fill", function(d) { return d.value.fill; });
// Add new legend text
entering.append("svg:text")
.attr("x", li.w / 2)
.attr("y", li.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(function (d) { return d.key; });
//g.exit().remove();
}
// Display or hide legend
function toggleLegend() {
var legend = d3.select("#legend");
if (legend.style("visibility") == "hidden") {
legend.style("visibility", "");
} else {
legend.style("visibility", "hidden");
}
}
// Disable on mouseover and mouseleave during click process
function click() {
if (updating == 0) {
var cont = d3.select("#container")
var path = d3.selectAll("path").on("mouseover", null);
if (lockStatus == 0) { // Lock current view
cont.on("mouseleave", null);
path.on("mouseover", null);
lockStatus = 1;
} else { // Unlock current view
cont.on("mouseleave", mouseleave);
path.on("mouseover", mouseover);
lockStatus = 0;
}
}
}
// Zoom in on specific area of chart
function dblclick(d) {
if (updating == 0) {
// Disable on click and double click events while we complete transition
updating = 1;
if (d.depth == 0) {
tweenActive = 0;
} else {
tweenActive = 1;
}
vis.transition("zoom")
.duration(dblclickDuration)
.tween("scale", function () {
var xd = d3.interpolate(x.domain(), [d.x0, d.x1]),
yd = d3.interpolate(y.domain(), [d.y0, 1]),
yr = d3.interpolate(y.range(), [d.y0 ? 20 : 0, radius]);
return function (t) {
x.domain(xd(t));
y.domain(yd(t)).range(yr(t));
};
})
.selectAll("path")
.attrTween("d", function (d) { return function () { return arc(d); }; });
setTimeout(function () {
updating = 0;
}, dblclickDuration + 10);
}
}
// Take a 2-column CSV and transform it into a hierarchical structure suitable
// for a partition layout. The first column is a sequence of step names, from
// root to leaf, separated by hyphens. The second column is a count of how
// often that sequence occurred.
function buildHierarchy(csv) {
var root = {"name": "root", "children": []};
for (var i = 0; i < csv.length; i++) {
var sequence = csv[i][0];
var size = +csv[i][1];
if (isNaN(size)) { // e.g. if this is a header row
continue;
}
var parts = sequence.split("-");
var currentNode = root;
for (var j = 0; j < parts.length; j++) {
var children = currentNode["children"];
var nodeName = parts[j];
var childNode;
if (j + 1 < parts.length) {
// Not yet at the end of the sequence; move down the tree.
var foundChild = false;
for (var k = 0; k < children.length; k++) {
if (children[k]["name"] == nodeName) {
childNode = children[k];
foundChild = true;
break;
}
}
// If we don't already have a child node for this branch, create it.
if (!foundChild) {
childNode = {"name": nodeName, "children": []};
children.push(childNode);
}
currentNode = childNode;
} else {
// Reached the end of the sequence; create a leaf node.
childNode = {"name": nodeName, "size": size};
children.push(childNode);
}
}
}
return root;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment