-
Notifications
You must be signed in to change notification settings - Fork 300
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
import code from electrify and edit to work with react
- Loading branch information
1 parent
9353002
commit 7f503e0
Showing
7 changed files
with
708 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import d3 from 'd3'; | ||
|
||
export const arc = d3.svg.arc() | ||
.startAngle(d => angle(d.x)) | ||
.endAngle(d => angle(d.x + Math.max(d.dx - 0.025, 0.0125))) | ||
.innerRadius(d => Math.sqrt(d.y)) | ||
.outerRadius(d => Math.sqrt(d.y + d.dy * 0.65)) | ||
|
||
export const initArc = d3.svg.arc() | ||
.startAngle(d => angle(d.x)) | ||
.endAngle(d => angle(d.x + d.dx)) | ||
.innerRadius(d => Math.sqrt(d.y)) | ||
.outerRadius(d => Math.sqrt(d.y)) | ||
|
||
export const angle = (x) => x | ||
|
||
// Modified version of d3's built-in bounce easing method: | ||
// https://github.com/mbostock/d3/blob/51228ccc4b54789f2d92d268e94716d1c016c774/src/interpolate/ease.js#L105-110 | ||
export const bounceHigh = (t) => t < 1 / 2.75 ? 7.5625 * t * t | ||
: t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .65 | ||
: t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .85 | ||
: 7.5625 * (t -= 2.625 / 2.75) * t + .975; | ||
|
||
export const arcTween = (a) => { | ||
const i = d3.interpolate({x: a.x0, dx: a.dx0}, a) | ||
return (t) => { | ||
const b = i(t) | ||
a.x0 = b.x | ||
a.dx0 = b.dx | ||
return arc(b) | ||
} | ||
} | ||
|
||
// | ||
// A more complex arc tween for handling | ||
// hover states. Returns a tween function | ||
// which returns an interpolator for each | ||
// datum. | ||
// | ||
export const hoverTween = (z) => { | ||
let ht = 0 | ||
const harc = d3.svg.arc() | ||
.startAngle(d => angle(d.x)) | ||
.endAngle(d => angle(d.x | ||
+ (1 - ht) * Math.max(d.dx - 0.025, 0.0125) | ||
+ ht * (d.dx + 0.00005) | ||
) | ||
) | ||
.innerRadius(d => Math.sqrt(d.y)) | ||
.outerRadius(d => Math.sqrt(d.y + d.dy * (ht * 0.35 + 0.65)) + ht) | ||
|
||
return (a) => { | ||
a.t0 = a.t3 = a.t0 || 0 | ||
a.t1 = z | ||
a.t2 = a.t1 - a.t0 | ||
return (_t) => { | ||
ht = a.t2 * _t + a.t3 | ||
a.t0 = ht | ||
return harc(a) | ||
} | ||
} | ||
} | ||
|
||
// | ||
// Makes it possible to rotate | ||
// angles greater than 180 degrees :) | ||
// | ||
export const rotateTween = (deg) => { | ||
return (d) => { | ||
return (t) => 'rotate(' + (1-t) * deg + ')' | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,282 @@ | ||
import d3 from 'd3'; | ||
import pretty from 'prettysize'; | ||
import schemes from './schemes'; | ||
import { arc, initArc, bounceHigh, arcTween, hoverTween, rotateTween } from './d3-utils'; | ||
import createModes, { highlightMode } from './mode'; | ||
|
||
const modeInitial = 'size' | ||
const modeFns = { | ||
count: () => 1 | ||
, size: (d) => d.size | ||
} | ||
|
||
export default function createD3Visualization(d3Data){ | ||
const domElements = d3Data.refs | ||
, root = d3Data.root | ||
, width = window.innerWidth * 0.8 | ||
, height = Math.max(window.innerHeight - 100, 100) * 0.8 | ||
, radius = Math.min(width, height) * 0.45 | ||
, deg = 120 | ||
|
||
const svg = d3.select(domElements.svg).append('div') | ||
.classed('svgContainer', true) | ||
.append('svg') | ||
.attr("preserveAspectRatio", "xMinYMin meet") //responsive SVG needs these 2 attr and no width or hight attr | ||
.attr("viewBox", "0 0 660 380") | ||
.style('overflow', 'visible') | ||
.append('g') | ||
.attr('transform', `translate(${width / 3.6},${height * .3})`) | ||
|
||
const partition = d3.layout.partition() | ||
.sort(null) | ||
.size([2 * Math.PI, radius * radius]) | ||
.value(modeFns[modeInitial]) | ||
|
||
// | ||
// Creates the title text in | ||
// the center of the rings. | ||
// | ||
const title = svg.append('text') | ||
.text(root.name) | ||
.attr('x', 0) | ||
.attr('y', -5) | ||
.style('font-size', '18px') | ||
.style('fill', 'white') | ||
.style('font-weight', 500) | ||
.style('alignment-baseline', 'middle') | ||
.style('text-anchor', 'middle') | ||
|
||
// | ||
// Likewise, this is the file | ||
// percentage size stat below the title | ||
// | ||
const percentageSize = svg.append('text') | ||
.text(pretty(root.value || root.size)) | ||
.attr('x', 0) | ||
.attr('y', 20) | ||
.style('fill', 'white') | ||
.style('font-size', '16px') | ||
.style('font-weight', 300) | ||
.style('alignment-baseline', 'middle') | ||
.style('text-anchor', 'middle') | ||
|
||
// | ||
// Likewise, this is the file | ||
// size stat below the title | ||
// | ||
const size = svg.append('text') | ||
.text('(' + pretty(root.value || root.size) + ')') | ||
.attr('x', 0) | ||
.attr('y', 40) | ||
.style('fill', 'white') | ||
.style('font-size', '16px') | ||
.style('alignment-baseline', 'middle') | ||
.style('text-anchor', 'middle') | ||
|
||
// | ||
// Each arc is wrapped in a group element, | ||
// to apply rotation transforms while | ||
// changing size and shape. | ||
// | ||
const groups = svg.datum(root).selectAll('g') | ||
.data(partition.nodes) | ||
.enter() | ||
.append('g') | ||
.attr('transform', `rotate(${deg})`) | ||
|
||
const maxdepth = groups[0].reduce((max, el) => Math.max(max, el.__data__.depth), 0) | ||
|
||
// | ||
// Actually create the arcs for each | ||
// file. | ||
// | ||
const path = groups.append('path') | ||
.attr('d', initArc) | ||
.attr('display', d => d.depth ? null : 'none') | ||
.style('stroke', '#2B2B2B') | ||
.style('stroke-width', '0') | ||
.style('fill-rule', 'evenodd') | ||
.each(function(d) { | ||
d.x0 = d.x | ||
d.dx0 = d.dx | ||
d.el = this | ||
}) | ||
|
||
let found = []; | ||
const _select = (node, selector) => { | ||
node.enabled = selector(node); | ||
if (node.enabled) { | ||
found.push(node); | ||
} | ||
if (node.children) { | ||
for (let c of node.children) { | ||
_select(c, selector); | ||
} | ||
} | ||
} | ||
_select(root, () => true); | ||
|
||
d3.select(domElements.search).on('keyup', function() { | ||
const text = this.value.replace(/^\s+/, "").replace(/\s+$/, "") | ||
if (text.length > 0) { | ||
found = []; | ||
const re = new RegExp(text, 'i'); | ||
_select(root, (node) => node.name.match(re) !== null); | ||
if (found.length === 1) { | ||
title.text(found[0].name) | ||
size.text(pretty(found[0].value || found[0].size)) | ||
} else { | ||
title.text("Multiple found") | ||
let completeSize = 0 | ||
for (let n of found) { | ||
completeSize += n.size; | ||
} | ||
size.text(`${pretty(completeSize)} total`) | ||
} | ||
} else { | ||
_select(root, () => true); | ||
} | ||
groups | ||
.select('path') | ||
.transition() | ||
.duration(200) | ||
.style('opacity', d => { | ||
console.log(d.name, d.enabled); | ||
return d.enabled ? 1.0 : 0.2 | ||
}) | ||
}) | ||
|
||
// | ||
// Colour scheme functionality. | ||
// | ||
// Triggered immediately with the default | ||
// scheme, must be passed a d3 selection. | ||
// | ||
let background | ||
, scheme = 0 | ||
, specials | ||
, color | ||
|
||
useScheme(scheme) | ||
function useScheme(n) { | ||
specials = schemes[n].specials | ||
|
||
const colors = schemes[n].main | ||
Object.keys(specials).forEach((key) => { | ||
const idx = colors.indexOf(specials[key].toLowerCase()) | ||
if (idx === -1) return | ||
colors.splice(idx, 1) | ||
}) | ||
|
||
color = d3.scale | ||
.ordinal() | ||
.range(colors) | ||
|
||
let _path = path.transition() | ||
.duration(600) | ||
.ease(bounceHigh, 1000) | ||
.delay(d => d.x * 100 + d.y / maxdepth * 0.06125); | ||
|
||
_path.style('fill', (d) => { | ||
const name = d.children ? d.name : d.parent.name | ||
d.c = schemes[n].modifier.call(d | ||
, specials[name] || color(name) | ||
, root | ||
) | ||
return d.c | ||
}) | ||
} | ||
|
||
let ptrans = 0 | ||
path.transition() | ||
.duration(1000) | ||
.each(() => ptrans++) | ||
.ease('elastic', 2, 1) | ||
.delay((d, i) => d.x * 100 + (i % 4) * 250 + d.y / maxdepth * 0.25) | ||
.attr('d', arc) | ||
.each('interrupt', () => { | ||
d3.select(domElements.search).transition().duration(200).style('opacity', 1) | ||
}) | ||
.each('end', () => { | ||
ptrans--; | ||
}) | ||
|
||
// | ||
// Rotates the newly created | ||
// arcs back towards their original | ||
// position. | ||
// | ||
let gtrans = 0 | ||
groups.transition() | ||
.duration(3250) | ||
.each(() => gtrans++) | ||
.delay((d, i) => d.x * 100 + (i % 4) * 250 + d.y / maxdepth * 0.25 + 250) | ||
.attrTween('transform', rotateTween(deg)) | ||
.each('end', () => { | ||
gtrans--; | ||
if (ptrans === 0 && gtrans === 0) { | ||
d3.select(domElements.search).transition().duration(200).style('opacity', 1) | ||
} | ||
}) | ||
|
||
groups.on('mouseover', (d) => { | ||
highlight(d) | ||
title.text(d.name) | ||
|
||
let sizeInPercentage = (d.value/root.value*100).toFixed(2); | ||
percentageSize.text(sizeInPercentage + " %") | ||
|
||
size.text('(' + pretty(d.value || d.size) + ')') | ||
}).on('mouseout', (d) => { | ||
unhighlight(d) | ||
title.text(root.name) | ||
size.text(pretty(root.value || root.size)) | ||
}) | ||
|
||
highlight.tween = hoverTween(1) | ||
function highlight(d) { | ||
if (d.el) d3.select(d.el) | ||
.transition() | ||
.delay(d => (d.depth - 1) * 300 / maxdepth) | ||
.ease('back-out', 10) | ||
.duration(500) | ||
.attrTween('d', highlight.tween) | ||
.style('fill', d => d.c) | ||
|
||
if (d.children) { | ||
let i = d.children.length | ||
while (i--) highlight(d.children[i]) | ||
} | ||
} | ||
|
||
unhighlight.tween = hoverTween(0) | ||
function unhighlight(d) { | ||
if (d.el) d3.select(d.el) | ||
.transition() | ||
.delay(d => (d.depth - 1) * 300 / maxdepth) | ||
.ease('back-out', 4) | ||
.duration(500) | ||
.attrTween('d', unhighlight.tween) | ||
.style('fill', d => d.c) | ||
|
||
if (d.children) { | ||
let i = d.children.length | ||
while (i--) unhighlight(d.children[i]) | ||
} | ||
} | ||
|
||
createModes(updateMode, domElements); | ||
|
||
updateMode(modeInitial, false) | ||
|
||
function updateMode(mode, update) { | ||
highlightMode(mode); | ||
if (!update) return | ||
groups | ||
.data(partition.value(modeFns[mode]).nodes) | ||
.select('path') | ||
.transition() | ||
.duration(1500) | ||
.attrTween('d', arcTween) | ||
} | ||
} |
Oops, something went wrong.