diff --git a/src/d3-renderers/visutil.js b/src/d3-renderers/utils.js similarity index 52% rename from src/d3-renderers/visutil.js rename to src/d3-renderers/utils.js index e707b08..7ef3fd9 100644 --- a/src/d3-renderers/visutil.js +++ b/src/d3-renderers/utils.js @@ -1,18 +1,18 @@ -function expandOrFoldSector(d3, g, d, self) { +function expandOrFoldSector(g, d, d3elem) { if (d.expand !== null && d.depth > 1) { //ignore root node and first level sectors if (d.expand === 0) { - if (d.children) d3.select(self).attr('opacity', 1); + if (d.children) d3elem.attr('opacity', 1); g.filter(function(a) { if (a.parent) - return a.parent.id === d.id; + return getValue(a.parent, 'id') === getValue(d, 'id'); }) .attr('visibility', 'visible'); d.expand = 1; } else { //if the sector is expanded if (d.children) - d3.select(self).attr('opacity', 1); + d3elem.attr('opacity', 1); hideSector(d, g); } } @@ -21,7 +21,7 @@ function expandOrFoldSector(d3, g, d, self) { function hideSector(d, g) { g.filter(function(a) { if (a.parent) - return a.parent.id === d.id; + return getValue(a.parent, 'id') === getValue(d, 'id'); }) .attr('visibility', 'hidden') .attr('opacity', 1) @@ -47,8 +47,34 @@ function isLocatedBottom(d) { return bottom; } +function getValue(d, prop) { + return d.data ? d.data[prop] : d[prop]; +} + +function getBarLengthFactor(d) { + var bar_length_factor = 10 / (d.depth - 2); + + //different bar_length factors + if (d.parent) { + if (d.parent.parent) { + if (getValue(d.parent.parent, 'id') === 'needs' || getValue(d.parent.parent, 'id') === 'values') { + bar_length_factor = 1; + } + if (d.parent.parent.parent) { + if (getValue(d.parent.parent.parent, 'id') === 'personality') { + bar_length_factor = 1; + } + } + } + } + + return bar_length_factor; +} + module.exports = { isLocatedBottom: isLocatedBottom, arc: arc, - expandOrFoldSector: expandOrFoldSector + expandOrFoldSector: expandOrFoldSector, + getValue: getValue, + getBarLengthFactor: getBarLengthFactor }; diff --git a/src/d3-renderers/v3/personality-chart-renderer.js b/src/d3-renderers/v3/personality-chart-renderer.js index 4a9a73d..765fcc8 100644 --- a/src/d3-renderers/v3/personality-chart-renderer.js +++ b/src/d3-renderers/v3/personality-chart-renderer.js @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* global alert */ 'use strict'; const d3 = require('d3'); Object.assign(d3, @@ -21,12 +20,10 @@ Object.assign(d3, ); const d3SvgSingleArc = require('../svg-single-arc'); -const visutil = require('../visutil'); +const utils = require('../utils'); function renderChart(widget) { - console.debug('personality-chart-renderer: defining renderChart'); - if (!widget.data) { - console.debug('personality-chart-renderer: data not defined.'); + if (!widget.data || !widget.loadingDiv) { return; } @@ -36,11 +33,6 @@ function renderChart(widget) { return; } - if (!widget.loadingDiv) { - alert('Widget is not fully initialized, cannot render BarsWidget'); - return; - } - widget.switchState(1); widget._layout(); @@ -52,146 +44,14 @@ function renderChart(widget) { function twoArcs(g) { g.each(function(d) { g = d3.select(this); - var right_pad = d.depth > 0 ? sector_right_pad / (3 * d.depth) : sector_right_pad; - - var score = widget.getScore(d), - score2 = 1; //for score sentiment data. it is the score of positive+netural - - //special render perception sector - if (d.perc_neu !== null && ((d.score + d.perc_neu) * d.dx < d.dx - sector_right_pad / (3 * d.depth))) { - score2 = d.score + d.perc_neu; - - d3.svg.arc() - .startAngle(function(d) { - return d.x + Math.abs(score2) * d.dx; - }) //x:startangle, - .endAngle(function(d) { - return d.x + d.dx - sector_right_pad / (3 * d.depth); - }) //dx: endangle, - .innerRadius(function(d) { - return sector_bottom_pad + d.y; - }) - .outerRadius(function(d) { - return d.y + d.dy; - }); - - right_pad = 0; - } - - var arc1_extend = (Math.abs(score) * d.dx - right_pad) > 0 ? (Math.abs(score) * d.dx - right_pad) : 0; - //Regular renders - var arc1 = d3.svg.arc() - .startAngle(function(d) { - return d.x; - }) //x:startangle, - .endAngle(function(d) { - return d.x + arc1_extend; - }) //dx: endangle, - .innerRadius(function(d) { - return sector_bottom_pad + d.y; - }) - .outerRadius(function(d) { - return d.y + d.dy; - }); - - var arc2 = d3.svg.arc() - .startAngle(function(d) { - return d.x + arc1_extend; - }) //x:startangle, - .endAngle(function(d) { - return d.x + Math.abs(score2) * d.dx - right_pad; - }) //dx: endangle, - .innerRadius(function(d) { - return sector_bottom_pad + d.y; - }) - .outerRadius(function(d) { - return d.y + d.dy; - }); - - //used for label path - var arc_for_label, arc_for_label_number; - var arc_label_radius, arc_label_number_radius; - if (d.depth === 1 && visutil.isLocatedBottom(d)) { - arc_label_radius = d.y + d.dy - (d.y + d.dy - sector_bottom_pad - d.y) / 6; - arc_label_number_radius = d.y + d.dy - (d.y + d.dy - sector_bottom_pad - d.y) / 8; - } else { - arc_label_radius = sector_bottom_pad + d.y + (d.y + d.dy - sector_bottom_pad - d.y) * 5 / 12; - arc_label_number_radius = d.y + d.dy - (d.y + d.dy - sector_bottom_pad - d.y) / 7; - } - - var bottom = visutil.isLocatedBottom(d); - if (bottom) { - //special reversed label for bottom data - arc_for_label = visutil.arc(d.x + d.dx - right_pad - Math.PI / 2, d.x - Math.PI / 2, arc_label_radius); - arc_for_label_number = visutil.arc(d.x + d.dx - right_pad - Math.PI / 2, d.x - Math.PI / 2, arc_label_number_radius); - } else { + widget.createParts(g, d); - arc_for_label = d3SvgSingleArc() - .startAngle(function(d) { - return d.x; - }) - .endAngle(function(d) { - return d.x + d.dx - right_pad; - }) - .radius(function(d) { - return d.depth === 1 ? d.y + d.dy - (d.y + d.dy - sector_bottom_pad - d.y) / 3 : sector_bottom_pad + d.y + (d.y + d.dy - sector_bottom_pad - d.y) * 3 / 5; - }); - - arc_for_label_number = d3SvgSingleArc() - .startAngle(function(d) { - return d.x; - }) - .endAngle(function(d) { - return d.x + d.dx - right_pad; - }) - .radius(function(d) { - return d.depth === 1 ? d.y + d.dy - (d.y + d.dy - sector_bottom_pad - d.y) / 3 : sector_bottom_pad + d.y + (d.y + d.dy - sector_bottom_pad - d.y) / 5; - }); - } - - d.coloridx = (d.depth === 1 || d.depth === 0) ? d.id : d.parent.coloridx; - var arc1color; - var outerRingColor, innerRingLightColor, innerRingDarkColor; - - if (d.coloridx === 'personality'){ - innerRingDarkColor = widget.COLOR_PALLETTE.traits_dark; - innerRingLightColor = widget.COLOR_PALLETTE.traits_light; - outerRingColor = widget.COLOR_PALLETTE.facet; - } - - if (d.coloridx === 'needs') { - innerRingDarkColor = widget.COLOR_PALLETTE.needs_dark; - innerRingLightColor = widget.COLOR_PALLETTE.needs_light; - outerRingColor = widget.COLOR_PALLETTE.need; - } - if (d.coloridx === 'values') { - innerRingDarkColor = widget.COLOR_PALLETTE.values_dark; - innerRingLightColor = widget.COLOR_PALLETTE.values_light; - outerRingColor = widget.COLOR_PALLETTE.value; - } - - arc1color = d.depth < 2 ? d3.color(innerRingLightColor) : d3.color(innerRingDarkColor); - var strokecolor = arc1color; + var right_pad = d.depth > 0 ? sector_right_pad / (3 * d.depth) : sector_right_pad; + var score = widget.getScore(d); - //if (!d.children && d.id !== 'srasrt' && d.id !== 'srclo' && d.id !== 'srdom') { if (!d.children) { - var bar_length_factor = 10 / (d.depth - 2); - - //different bar_length factors - if (d.parent) { - if (d.parent.parent) { - if (d.parent.parent.id === 'needs' || d.parent.parent.id === 'values') { - bar_length_factor = 1; - } - if (d.parent.parent.parent) - if (d.parent.parent.parent.id === 'personality') bar_length_factor = 1; - } else { - console.debug(d.name + ': Parent is null!'); - } - } - var inner_r = sector_bottom_pad + d.y; - var out_r = sector_bottom_pad + d.y + bar_length_factor * Math.abs(score) * d.dy; + var out_r = sector_bottom_pad + d.y + utils.getBarLengthFactor(d) * Math.abs(score) * d.dy; var _bar = d3.svg.arc() .startAngle(d.x) @@ -199,15 +59,7 @@ function renderChart(widget) { .innerRadius(inner_r) .outerRadius(out_r); // Draw leaf arcs - if (!widget._childElements.paths[widget.getUniqueId(d, 'bar')]) { - widget._childElements.paths[widget.getUniqueId(d, 'bar')] = g.append('path') - .attr('class', '_bar') - .style('stroke', '#EEE') - .style('fill', function() { - return d3.color(outerRingColor); - }); - } - widget._childElements.paths[widget.getUniqueId(d, 'bar')] + widget._childElements.parts[widget.getUniqueId(d, 'bar')] .attr('d', _bar); //add label; @@ -225,113 +77,92 @@ function renderChart(widget) { dy_init = 5 + d.dx * 20 * Math.PI; } - var max_label_size = 13, - lable_size = 10; - - if ((7.5 + 15 * Math.PI * d.dx) > max_label_size) { - lable_size = max_label_size; - } - - if (!widget._childElements.texts[widget.getUniqueId(d, 'sector_leaf_text')]) { - widget._childElements.texts[widget.getUniqueId(d, 'sector_leaf_text')] = g.append('text') - .attr('class', 'sector_leaf_text'); - } - widget._childElements.texts[widget.getUniqueId(d, 'sector_leaf_text')] + widget._childElements.parts[widget.getUniqueId(d, 'sector_leaf_text')] .attr('dy', dy_init) - .attr('font-size', lable_size) .attr('text-anchor', lbl_anchor) .attr('transform', 'translate(' + (out_r + 5) * Math.sin(d.x) + ',' + (-(out_r + 5) * Math.cos(d.x)) + ') ' + 'rotate(' + rotate + ')'); } else { - //non-bar/non-leaf sector - if (!widget._childElements.paths[widget.getUniqueId(d, 'arc1')]) { - widget._childElements.paths[widget.getUniqueId(d, 'arc1')] = g.append('path') - .attr('class', 'arc1') - .style('stroke', strokecolor) // was: arc1color - .style('fill', arc1color); - } - widget._childElements.paths[widget.getUniqueId(d, 'arc1')] + var arc1_extend = (Math.abs(score) * d.dx - right_pad) > 0 ? (Math.abs(score) * d.dx - right_pad) : 0; + //Regular renders + var arc1 = d3.svg.arc() + .startAngle(function(d) { + return d.x; + }) //x:startangle, + .endAngle(function(d) { + return d.x + arc1_extend; + }) //dx: endangle, + .innerRadius(function(d) { + return sector_bottom_pad + d.y; + }) + .outerRadius(function(d) { + return d.y + d.dy; + }); + widget._childElements.parts[widget.getUniqueId(d, 'arc1')] .attr('d', arc1); - if (!widget._childElements.paths[widget.getUniqueId(d, 'arc2')]) { - widget._childElements.paths[widget.getUniqueId(d, 'arc2')] = g.append('path') - .attr('class', 'arc2') - .style('stroke', strokecolor) // was: arc1color - .style('fill', arc1color) - .style('fill-opacity', 0.15); - } - widget._childElements.paths[widget.getUniqueId(d, 'arc2')] + var arc2 = d3.svg.arc() + .startAngle(function(d) { + return d.x + arc1_extend; + }) //x:startangle, + .endAngle(function(d) { + return d.x + d.dx - right_pad; + }) //dx: endangle, + .innerRadius(function(d) { + return sector_bottom_pad + d.y; + }) + .outerRadius(function(d) { + return d.y + d.dy; + }); + widget._childElements.parts[widget.getUniqueId(d, 'arc2')] .attr('d', arc2); - //draw label: - //path used for label - if (!widget._childElements.paths[widget.getUniqueId(d, 'arc_for_label')]) { - widget._childElements.paths[widget.getUniqueId(d, 'arc_for_label')] = g.append('path') - .attr('class', 'arc_for_label') - .attr('id', function(d) { - return widget.getUniqueId(d, 'arc_for_label'); - }) - .style('stroke-opacity', 0) - .style('fill-opacity', 0); + //used for label path + var bottom = utils.isLocatedBottom(d); + var arc_for_label, arc_for_label_number; + var arc_label_radius, arc_label_number_radius; + if (d.depth === 1 && bottom) { + arc_label_radius = d.y + d.dy - (d.y + d.dy - sector_bottom_pad - d.y) / 6; + arc_label_number_radius = d.y + d.dy - (d.y + d.dy - sector_bottom_pad - d.y) / 8; + } else { + arc_label_radius = sector_bottom_pad + d.y + (d.y + d.dy - sector_bottom_pad - d.y) * 5 / 12; + arc_label_number_radius = d.y + d.dy - (d.y + d.dy - sector_bottom_pad - d.y) / 7; } - widget._childElements.paths[widget.getUniqueId(d, 'arc_for_label')] - .attr('d', arc_for_label); + if (bottom) { + //special reversed label for bottom data + arc_for_label = utils.arc(d.x + d.dx - right_pad - Math.PI / 2, d.x - Math.PI / 2, arc_label_radius); + arc_for_label_number = utils.arc(d.x + d.dx - right_pad - Math.PI / 2, d.x - Math.PI / 2, arc_label_number_radius); + } else { + arc_for_label = d3SvgSingleArc() + .startAngle(function(d) { + return d.x; + }) + .endAngle(function(d) { + return d.x + d.dx - right_pad; + }) + .radius(function(d) { + return d.depth === 1 ? d.y + d.dy - (d.y + d.dy - sector_bottom_pad - d.y) / 3 : sector_bottom_pad + d.y + (d.y + d.dy - sector_bottom_pad - d.y) * 3 / 5; + }); - //add label - if (!widget._childElements.texts[widget.getUniqueId(d, 'sector_label_path')]) { - widget._childElements.texts[widget.getUniqueId(d, 'sector_label_path')] = g.append('text') - .attr('class', 'sector_label') - .attr('visibility', function(d) { - return d.depth === 1 ? 'visible' : null; + arc_for_label_number = d3SvgSingleArc() + .startAngle(function(d) { + return d.x; }) - .attr('class', 'sector_nonleaf_text') - .append('textPath') - .attr('class', 'sector_label_path') - .attr('position-in-sector', d.depth <= 1 ? 'center' : (bottom ? 'inner' : 'outer')) // Since both text lines share the same 'd', this class annotation tells where is the text, helping to determine the real arc length - .attr('font-size', function(d) { - return 30 / Math.sqrt(d.depth + 1); + .endAngle(function(d) { + return d.x + d.dx - right_pad; }) - .attr('xlink:href', function(d) { - return '#' + widget.getUniqueId(d, 'arc_for_label'); + .radius(function(d) { + return d.depth === 1 ? d.y + d.dy - (d.y + d.dy - sector_bottom_pad - d.y) / 3 : sector_bottom_pad + d.y + (d.y + d.dy - sector_bottom_pad - d.y) / 5; }); } + widget._childElements.parts[widget.getUniqueId(d, 'arc_for_label')] + .attr('d', arc_for_label); + //draw label number //path used for label number if (d.depth > 1) { - if (!widget._childElements.paths[widget.getUniqueId(d, 'arc_for_label_number')]) { - widget._childElements.paths[widget.getUniqueId(d, 'arc_for_label_number')] = g.append('path') - .attr('class', 'arc_for_label_number') - // NOTE HB: adding widget.id so we to avoid name clashing - .attr('id', function(d) { - return widget.getUniqueId(d, 'arc_for_label_number'); - }) - .style('stroke-opacity', 0) - .style('fill-opacity', 0); - } - widget._childElements.paths[widget.getUniqueId(d, 'arc_for_label_number')] + widget._childElements.parts[widget.getUniqueId(d, 'arc_for_label_number')] .attr('d', arc_for_label_number); - - //add label - if (!widget._childElements.texts[widget.getUniqueId(d, 'sector_label_number_path')]) { - widget._childElements.texts[widget.getUniqueId(d, 'sector_label_number_path')] = g.append('text') - .attr('class', 'sector_label_number ') - .attr('visibility', function(d) { - return d.depth === 1 ? 'visible' : null; - }) - //.attr('font-family','sans-serif') - .attr('class', 'sector_nonleaf_text') - //.attr('fill', d3.rgb(arc1color).darker(2)) - .append('textPath') - .attr('class', 'sector_label_number_path') - .attr('position-in-sector', bottom ? 'outer' : 'inner') // Since both text lines share the same 'd', this class annotation tells where is the text, helping to determine the real arc length - .attr('font-size', function() { - return 10; - }) - // NOTE HB: Why do we need this xlink:href? In any case, adding widget.id so we to avoid name clashing - .attr('xlink:href', function(d) { - return '#' + widget.getUniqueId(d, 'arc_for_label_number'); - }); - } } } }); @@ -340,17 +171,21 @@ function renderChart(widget) { return twoArcs; } - var width = widget.dimW, - height = widget.dimH; // The flower had a radius of 640 / 1.9 = 336.84 in the original. - var radius = Math.min(width, height) / 3.2; + var radius = Math.min(widget.dimW, widget.dimH) / 3.2; var sector = twoArcsRender(radius); // Center the graph of 'g' widget.vis = widget.d3vis.append('g') - .attr('transform', 'translate(' + (width / 2) + ',' + height / 2 + ')') + .attr('transform', 'translate(' + (widget.dimW / 2) + ',' + widget.dimH / 2 + ')') .append('g'); + var profile = { + children: tree.children.filter(function (child) { + return widget.exclude.indexOf(child.id) === -1; + }) + }; + var partition = d3.layout.partition() .sort(null) .size([2 * Math.PI, radius]) @@ -359,16 +194,6 @@ function renderChart(widget) { return d.size; }); - var profile = { - children: tree.children - }; - - // exclude specified sectors - var exclude = widget.exclude; - profile.children = profile.children.filter(function (child) { - return exclude.indexOf(child.id) === -1; - }); - var g = widget.vis.data([profile]).selectAll('g') .data(partition.nodes) .enter().append('g') @@ -381,7 +206,7 @@ function renderChart(widget) { d.expand = 1; }) .on('click', function(d) { - visutil.expandOrFoldSector(d3, g, d, this); + utils.expandOrFoldSector(g, d, d3.select(this)); }) .on('mouseover', function(d) { widget.showTooltip(d, this); diff --git a/src/d3-renderers/v4/personality-chart-renderer.js b/src/d3-renderers/v4/personality-chart-renderer.js index 6fa6515..6c4964d 100644 --- a/src/d3-renderers/v4/personality-chart-renderer.js +++ b/src/d3-renderers/v4/personality-chart-renderer.js @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* global alert */ 'use strict'; const d3 = Object.assign({}, require('d3-color'), @@ -23,11 +22,10 @@ const d3 = Object.assign({}, ); const d3SvgSingleArc = require('../svg-single-arc'); -const visutil = require('../visutil'); +const utils = require('../utils'); function renderChart(widget) { - if (!widget.data) { - console.error('personality-chart-renderer: data not defined.'); + if (!widget.data || !widget.loadingDiv) { return; } @@ -37,11 +35,6 @@ function renderChart(widget) { return; } - if (!widget.loadingDiv) { - alert('Widget is not fully initialized, cannot render BarsWidget'); - return; - } - widget.switchState(1); widget._layout(); @@ -53,148 +46,14 @@ function renderChart(widget) { function twoArcs(g) { g.each(function(d) { g = d3.select(this); + widget.createParts(g, d); var right_pad = d.depth > 0 ? sector_right_pad / (3 * d.depth) : sector_right_pad; + var score = widget.getScore(d); - var score = widget.getScore(d), - score2 = 1; //for score sentiment data. it is the score of positive+netural - - //special render perception sector - if (d.perc_neu !== null && (((d.score + d.perc_neu) * (d.x1 - d.x0)) < ((d.x1 - d.x0) - sector_right_pad / (3 * d.depth)))) { - score2 = d.score + d.perc_neu; - - d3.arc() - .startAngle(function(d) { - return d.x0 + Math.abs(score2) * (d.x1 - d.x0); - }) //x:startangle, - .endAngle(function(d) { - return d.x1 - sector_right_pad / (3 * d.depth); - }) //dx: endangle, - .innerRadius(function(d) { - return sector_bottom_pad + d.y0; - }) - .outerRadius(function(d) { - return d.y1; - }); - - right_pad = 0; - } - - var arc1_extend = Math.max(Math.abs(score) * (d.x1 - d.x0) - right_pad, 0); - //Regular renders - var arc1 = d3.arc() - .startAngle(function(d) { - return d.x0; - }) //x:startangle, - .endAngle(function(d) { - return d.x0 + arc1_extend; - }) //dx: endangle, - .innerRadius(function(d) { - return sector_bottom_pad + d.y0; - }) - .outerRadius(function(d) { - return d.y1; - }); - - var arc2 = d3.arc() - .startAngle(function(d) { - return d.x0 + arc1_extend; - }) //x:startangle, - .endAngle(function(d) { - return d.x0 + Math.abs(score2) * (d.x1 - d.x0) - right_pad; - }) //dx: endangle, - .innerRadius(function(d) { - return sector_bottom_pad + d.y0; - }) - .outerRadius(function(d) { - return d.y1; - }); - - //used for label path - var arc_for_label, arc_for_label_number; - var arc_label_radius, arc_label_number_radius; - if (d.depth === 1 && visutil.isLocatedBottom(d)) { - arc_label_radius = d.y1 - (d.y1 - sector_bottom_pad - d.y0) / 6; - arc_label_number_radius = d.y1 - (d.y1 - sector_bottom_pad - d.y0) / 8; - } else { - arc_label_radius = sector_bottom_pad + d.y0 + (d.y1 - sector_bottom_pad - d.y0) * 5 / 12; - arc_label_number_radius = d.y1 - (d.y1 - sector_bottom_pad - d.y0) / 7; - } - - var bottom = visutil.isLocatedBottom(d); - if (bottom) { - //special reversed label for bottom data - arc_for_label = visutil.arc(d.x1 - right_pad - Math.PI / 2, d.x0- Math.PI / 2, arc_label_radius); - arc_for_label_number = visutil.arc(d.x1 - right_pad - Math.PI / 2, d.x0- Math.PI / 2, arc_label_number_radius); - } else { - - arc_for_label = d3SvgSingleArc() - .startAngle(function(d) { - return d.x0; - }) - .endAngle(function(d) { - return d.x1 - right_pad; - }) - .radius(function(d) { - return d.depth === 1 ? d.y1 - (d.y1 - sector_bottom_pad - d.y0) / 3 : sector_bottom_pad + d.y0 + (d.y1 - sector_bottom_pad - d.y0) * 3 / 5; - }); - - arc_for_label_number = d3SvgSingleArc() - .startAngle(function(d) { - return d.x0; - }) - .endAngle(function(d) { - return d.x1 - right_pad; - }) - .radius(function(d) { - return d.depth === 1 ? d.y1 - (d.y1 - sector_bottom_pad - d.y0) / 3 : sector_bottom_pad + d.y0 + (d.y1 - sector_bottom_pad - d.y0) / 5; - }); - } - - d.coloridx = (d.depth === 1 || d.depth === 0) ? d.id : d.parent.coloridx; - var arc1color; - var outerRingColor, innerRingLightColor, innerRingDarkColor; - - if (d.coloridx === 'personality'){ - innerRingDarkColor = widget.COLOR_PALLETTE.traits_dark; - innerRingLightColor = widget.COLOR_PALLETTE.traits_light; - outerRingColor = widget.COLOR_PALLETTE.facet; - } - - if (d.coloridx === 'needs') { - innerRingDarkColor = widget.COLOR_PALLETTE.needs_dark; - innerRingLightColor = widget.COLOR_PALLETTE.needs_light; - outerRingColor = widget.COLOR_PALLETTE.need; - } - if (d.coloridx === 'values') { - innerRingDarkColor = widget.COLOR_PALLETTE.values_dark; - innerRingLightColor = widget.COLOR_PALLETTE.values_light; - outerRingColor = widget.COLOR_PALLETTE.value; - } - - arc1color = d.depth < 2 ? d3.color(innerRingLightColor) : d3.color(innerRingDarkColor); - var strokecolor = arc1color; - - //if (!d.children && d.id !== 'srasrt' && d.id !== 'srclo' && d.id !== 'srdom') { if (!d.children) { - score = d.score; - var bar_length_factor = 10 / (d.depth - 2); - - //different bar_length factors - if (d.parent) { - if (d.parent.parent) { - if (d.parent.parent.id === 'needs' || d.parent.parent.id === 'values') { - bar_length_factor = 1; - } - if (d.parent.parent.parent) - if (d.parent.parent.parent.id === 'personality') bar_length_factor = 1; - } else { - console.debug(d.name + ': Parent is null!'); - } - } - var inner_r = sector_bottom_pad + d.y0; - var out_r = sector_bottom_pad + d.y0 + bar_length_factor * Math.abs(score) * (d.y1 - d.y0); + var out_r = sector_bottom_pad + d.y0 + utils.getBarLengthFactor(d) * Math.abs(score) * (d.y1 - d.y0); var _bar = d3.arc() .startAngle(d.x0) @@ -202,15 +61,7 @@ function renderChart(widget) { .innerRadius(inner_r) .outerRadius(out_r); // Draw leaf arcs - if (!widget._childElements.paths[widget.getUniqueId(d, 'bar')]) { - widget._childElements.paths[widget.getUniqueId(d, 'bar')] = g.append('path') - .attr('class', '_bar') - .style('stroke', '#EEE') - .style('fill', function() { - return d3.color(outerRingColor); - }); - } - widget._childElements.paths[widget.getUniqueId(d, 'bar')] + widget._childElements.parts[widget.getUniqueId(d, 'bar')] .attr('d', _bar); //add label; @@ -228,112 +79,89 @@ function renderChart(widget) { dy_init = 5 + (d.x1 - d.x0) * 20 * Math.PI; } - var max_label_size = 13, - lable_size = 10; - - if ((7.5 + 15 * Math.PI * (d.x1 - d.x0)) > max_label_size) { - lable_size = max_label_size; - } - - if (!widget._childElements.texts[widget.getUniqueId(d, 'sector_leaf_text')]) { - widget._childElements.texts[widget.getUniqueId(d, 'sector_leaf_text')] = g.append('text') - .attr('class', 'sector_leaf_text'); - } - widget._childElements.texts[widget.getUniqueId(d, 'sector_leaf_text')] + widget._childElements.parts[widget.getUniqueId(d, 'sector_leaf_text')] .attr('dy', dy_init) - .attr('font-size', lable_size) .attr('text-anchor', lbl_anchor) .attr('transform', 'translate(' + (out_r + 5) * Math.sin(d.x0) + ',' + (-(out_r + 5) * Math.cos(d.x0)) + ') ' + 'rotate(' + rotate + ')'); - } else { - //non-bar/non-leaf sector - if (!widget._childElements.paths[widget.getUniqueId(d, 'arc1')]) { - widget._childElements.paths[widget.getUniqueId(d, 'arc1')] = g.append('path') - .attr('class', 'arc1') - .style('stroke', strokecolor) // was: arc1color - .style('fill', arc1color); - } - widget._childElements.paths[widget.getUniqueId(d, 'arc1')] + var arc1_extend = Math.max(Math.abs(score) * (d.x1 - d.x0) - right_pad, 0); + //Regular renders + var arc1 = d3.arc() + .startAngle(function(d) { + return d.x0; + }) //x:startangle, + .endAngle(function(d) { + return d.x0 + arc1_extend; + }) //dx: endangle, + .innerRadius(function(d) { + return sector_bottom_pad + d.y0; + }) + .outerRadius(function(d) { + return d.y1; + }); + + widget._childElements.parts[widget.getUniqueId(d, 'arc1')] .attr('d', arc1); - if (!widget._childElements.paths[widget.getUniqueId(d, 'arc2')]) { - widget._childElements.paths[widget.getUniqueId(d, 'arc2')] = g.append('path') - .attr('class', 'arc2') - .style('stroke', strokecolor) // was: arc1color - .style('fill', arc1color) - .style('fill-opacity', 0.15); - } - widget._childElements.paths[widget.getUniqueId(d, 'arc2')] + var arc2 = d3.arc() + .startAngle(function(d) { + return d.x0 + arc1_extend; + }) //x:startangle, + .endAngle(function(d) { + return d.x0 + (d.x1 - d.x0) - right_pad; + }) //dx: endangle, + .innerRadius(function(d) { + return sector_bottom_pad + d.y0; + }) + .outerRadius(function(d) { + return d.y1; + }); + widget._childElements.parts[widget.getUniqueId(d, 'arc2')] .attr('d', arc2); - //draw label: - //path used for label - if (!widget._childElements.paths[widget.getUniqueId(d, 'arc_for_label')]) { - widget._childElements.paths[widget.getUniqueId(d, 'arc_for_label')] = g.append('path') - .attr('class', 'arc_for_label') - // NOTE HB: adding widget.id so we to avoid name clashing - .attr('id', function(d) { - return widget.id + '_' + d.id + '.arc_for_label'; - }) - .style('stroke-opacity', 0) - .style('fill-opacity', 0); + var bottom = utils.isLocatedBottom(d); + var arc_for_label, arc_for_label_number; + var arc_label_radius, arc_label_number_radius; + if (d.depth === 1 && bottom) { + arc_label_radius = d.y1 - (d.y1 - sector_bottom_pad - d.y0) / 6; + arc_label_number_radius = d.y1 - (d.y1 - sector_bottom_pad - d.y0) / 8; + } else { + arc_label_radius = sector_bottom_pad + d.y0 + (d.y1 - sector_bottom_pad - d.y0) * 5 / 12; + arc_label_number_radius = d.y1 - (d.y1 - sector_bottom_pad - d.y0) / 7; } - widget._childElements.paths[widget.getUniqueId(d, 'arc_for_label')] - .attr('d', arc_for_label); - //add label - if (!widget._childElements.texts[widget.getUniqueId(d, 'sector_label')]) { - widget._childElements.texts[widget.getUniqueId(d, 'sector_label')] = g.append('text') - .attr('class', 'sector_label') - .attr('visibility', function(d) { - return d.depth === 1 ? 'visible' : null; + if (bottom) { + //special reversed label for bottom data + arc_for_label = utils.arc(d.x1 - right_pad - Math.PI / 2, d.x0 - Math.PI / 2, arc_label_radius); + arc_for_label_number = utils.arc(d.x1 - right_pad - Math.PI / 2, d.x0 - Math.PI / 2, arc_label_number_radius); + } else { + arc_for_label = d3SvgSingleArc() + .startAngle(function(d) { + return d.x0; }) - .attr('class', 'sector_nonleaf_text') - .append('textPath') - .attr('class', 'sector_label_path') - .attr('position-in-sector', d.depth <= 1 ? 'center' : (bottom ? 'inner' : 'outer')) // Since both text lines share the same 'd', this class annotation tells where is the text, helping to determine the real arc length - .attr('font-size', function(d) { - return 30 / Math.sqrt(d.depth + 1); + .endAngle(function(d) { + return d.x1 - right_pad; }) - .attr('xlink:href', function(d) { - return '#' + widget.id + '_' + d.id + '.arc_for_label'; + .radius(function(d) { + return d.depth === 1 ? d.y1 - (d.y1 - sector_bottom_pad - d.y0) / 3 : sector_bottom_pad + d.y0 + (d.y1 - sector_bottom_pad - d.y0) * 3 / 5; }); - } - //draw label number - //path used for label number + arc_for_label_number = d3SvgSingleArc() + .startAngle(function(d) { + return d.x0; + }) + .endAngle(function(d) { + return d.x1 - right_pad; + }) + .radius(function(d) { + return d.depth === 1 ? d.y1 - (d.y1 - sector_bottom_pad - d.y0) / 3 : sector_bottom_pad + d.y0 + (d.y1 - sector_bottom_pad - d.y0) / 5; + }); + } + widget._childElements.parts[widget.getUniqueId(d, 'arc_for_label')] + .attr('d', arc_for_label); if (d.depth > 1) { - if (!widget._childElements.paths[widget.getUniqueId(d, 'arc_for_label_number')]) { - widget._childElements.paths[widget.getUniqueId(d, 'arc_for_label_number')] = g.append('path') - .attr('class', 'arc_for_label_number') - .attr('id', function(d) { - return widget.id + '_' + d.id + '.arc_for_label_number'; - }) - .style('stroke-opacity', 0) - .style('fill-opacity', 0); - } - widget._childElements.paths[widget.getUniqueId(d, 'arc_for_label_number')] + widget._childElements.parts[widget.getUniqueId(d, 'arc_for_label_number')] .attr('d', arc_for_label_number); - - //add label - if (!widget._childElements.texts[widget.getUniqueId(d, 'sector_label_number')]) { - widget._childElements.texts[widget.getUniqueId(d, 'sector_label_number')] = g.append('text') - .attr('class', 'sector_label_number ') - .attr('visibility', function(d) { - return d.depth === 1 ? 'visible' : null; - }) - .attr('class', 'sector_nonleaf_text') - .append('textPath') - .attr('class', 'sector_label_number_path') - .attr('position-in-sector', bottom ? 'outer' : 'inner') // Since both text lines share the same 'd', this class annotation tells where is the text, helping to determine the real arc length - .attr('font-size', function() { - return 10; - }) - // NOTE HB: Why do we need this xlink:href? In any case, adding widget.id so we to avoid name clashing - .attr('xlink:href', function(d) { - return '#' + widget.id + '_' + d.id + '.arc_for_label_number'; - }); - } } } }); @@ -342,33 +170,26 @@ function renderChart(widget) { return twoArcs; } - var width = widget.dimW, - height = widget.dimH; // The flower had a radius of 640 / 1.9 = 336.84 in the original. - var radius = Math.min(width, height) / 3.2; + var radius = Math.min(widget.dimW, widget.dimH) / 3.2; var sector = twoArcsRender(radius); // Center the graph of 'g' widget.vis = widget.d3vis.append('g') - .attr('transform', 'translate(' + (width / 2) + ',' + height / 2 + ')') + .attr('transform', 'translate(' + (widget.dimW / 2) + ',' + widget.dimH / 2 + ')') .append('g'); var profile = { - children: tree.children + children: tree.children.filter(function (child) { + return widget.exclude.indexOf(child.id) === -1; + }) }; - // exclude specified sectors - var exclude = widget.exclude; - profile.children = profile.children.filter(function (child) { - return exclude.indexOf(child.id) === -1; - }); - - var root = d3.hierarchy([profile]) + var root = d3.hierarchy(profile) .sum(function(d) { if (!d.hasOwnProperty('size') && !d.hasOwnProperty('children')) return 1; return d.size; - }) - .sort(null); + }); var partition = d3.partition() .size([2 * Math.PI, radius]); @@ -385,7 +206,7 @@ function renderChart(widget) { d.expand = 1; }) .on('click', function(d) { - visutil.expandOrFoldSector(d3, g, d, this); + utils.expandOrFoldSector(g, d, d3.select(this)); }) .on('mouseover', function(d) { widget.showTooltip(d, this); diff --git a/src/widget.js b/src/widget.js index 11257b5..96797e0 100644 --- a/src/widget.js +++ b/src/widget.js @@ -17,6 +17,7 @@ 'use strict'; const colors = require('./utilities/colors'); +const utils = require('./d3-renderers/utils'); class SunburstWidget { @@ -41,8 +42,7 @@ class SunburstWidget { image: null, pattern: null, circle: null, - texts: {}, - paths: {} + parts: {} }; } @@ -124,36 +124,36 @@ class SunburstWidget { var self = this; tree.children.forEach(function(child) { - if (self._childElements.texts[self.getUniqueId(child, 'sector_label_path')]) { - self._childElements.texts[self.getUniqueId(child, 'sector_label_path')].text(self.getNameLabelText(child)); + if (self._childElements.parts[self.getUniqueId(child, 'sector_label_path')]) { + self._childElements.parts[self.getUniqueId(child, 'sector_label_path')].text(self.getNameLabelText(child)); } child.children.forEach(function(category) { - if (self._childElements.texts[self.getUniqueId(category, 'sector_label_path')]) { - self._childElements.texts[self.getUniqueId(category, 'sector_label_path')].text(category.name); + if (self._childElements.parts[self.getUniqueId(category, 'sector_label_path')]) { + self._childElements.parts[self.getUniqueId(category, 'sector_label_path')].text(category.name); } - if (self._childElements.texts[self.getUniqueId(category, 'sector_label_number_path')]) { - self._childElements.texts[self.getUniqueId(category, 'sector_label_number_path')].text(self.getScoreLabelText(category)); + if (self._childElements.parts[self.getUniqueId(category, 'sector_label_number_path')]) { + self._childElements.parts[self.getUniqueId(category, 'sector_label_number_path')].text(self.getScoreLabelText(category)); } category.children.forEach(function(trait) { if (trait.category === 'personality') { // personality traits - if (self._childElements.texts[self.getUniqueId(trait, 'sector_label_path')]) { - self._childElements.texts[self.getUniqueId(trait, 'sector_label_path')].text(trait.name); + if (self._childElements.parts[self.getUniqueId(trait, 'sector_label_path')]) { + self._childElements.parts[self.getUniqueId(trait, 'sector_label_path')].text(trait.name); } - if (self._childElements.texts[self.getUniqueId(trait, 'sector_label_number_path')]) { - self._childElements.texts[self.getUniqueId(trait, 'sector_label_number_path')].text(self.getScoreLabelText(trait)); + if (self._childElements.parts[self.getUniqueId(trait, 'sector_label_number_path')]) { + self._childElements.parts[self.getUniqueId(trait, 'sector_label_number_path')].text(self.getScoreLabelText(trait)); } trait.children.forEach(function(facet) { - if (self._childElements.texts[self.getUniqueId(facet, 'sector_leaf_text')]) { - self._childElements.texts[self.getUniqueId(facet, 'sector_leaf_text')].text(self.getNameLabelText(facet)); + if (self._childElements.parts[self.getUniqueId(facet, 'sector_leaf_text')]) { + self._childElements.parts[self.getUniqueId(facet, 'sector_leaf_text')].text(self.getNameLabelText(facet)); } }); } else { - if (self._childElements.texts[self.getUniqueId(trait, 'sector_leaf_text')]) { - self._childElements.texts[self.getUniqueId(trait, 'sector_leaf_text')].text(self.getNameLabelText(trait)); + if (self._childElements.parts[self.getUniqueId(trait, 'sector_leaf_text')]) { + self._childElements.parts[self.getUniqueId(trait, 'sector_leaf_text')].text(self.getNameLabelText(trait)); } } }); @@ -180,12 +180,12 @@ class SunburstWidget { var curNd = d3this.node(); var text = d3this.text(); if (text && text.length > 0) { - var position = self.d3.select(this).attr('position-in-sector'); // 'inner' or 'outer' + var position = self.d3.select(this).attr('position-in-sector'); var frac = position === 'center' ? 0.5 : position === 'outer' ? 2 / 3 : 1 / 3; var sector_length = self._d3version === 'v3' ? (d.y + d.dy * frac) * d.dx : - (d.y1 * frac) * (d.x1 - d.x0); - var text_length = curNd.getComputedTextLength(); //+margin; + (d.y0 + (d.y1 - d.y0) * frac) * (d.x1 - d.x0); + var text_length = curNd.getComputedTextLength(); var cur_font_size = self.d3.select(this).attr('font-size'); var new_font_size = cur_font_size * sector_length / text_length; @@ -273,41 +273,48 @@ class SunburstWidget { } getScore(d) { - var score = d.score; - if (score === null) score = 1; - if (score >= 1) { - score = 0.99; - } else if (score <= -1) { - score = -0.99; - } - - //for request without any result - if (d.name === '') { + var score = utils.getValue(d, 'score'); + var name = utils.getValue(d, 'name'); + if (typeof score === 'undefined' || typeof name === 'undefined') { score = 0; + } else { + if (score === null) { + score = 0.99; + } else if (score >= 1) { + score = 0.99; + } else if (score <= -1) { + score = -0.99; + } } return score; } getScoreLabelText(d) { - return d.score === null || isNaN(d.score) ? '' : ' (' + (this.getScore(d) * 100).toFixed(0) + '%)'; + var score = utils.getValue(d, 'score'); + return score === null || isNaN(score) ? '' : ' (' + (this.getScore(d) * 100).toFixed(0) + '%)'; } getNameLabelText(d) { - if (!d.name) { + var name = utils.getValue(d, 'name'); + if (!name) { return ''; } - var label = d.name, score = this.getScore(d); - if (d.id === 'sbh_dom' || d.id === 'sbh_parent'){ - label = d.name; - } else if (d.category === 'values') { - label = d.name + ((score * 100).toFixed(0) === 'NaN' || isNaN(score) ? '' : ' (' + (score * 100).toFixed(0) + '%)'); + var score = this.getScore(d); + var id = utils.getValue(d, 'id'); + var category = utils.getValue(d, 'category'); + var label = name; + + if (id === 'sbh_dom' || id === 'sbh_parent'){ + label = name; + } else if (category === 'values') { + label = name + ((score * 100).toFixed(0) === 'NaN' || isNaN(score) ? '' : ' (' + (score * 100).toFixed(0) + '%)'); } else { - label = d.name + ((score * 100).toFixed(0) === 'NaN' || isNaN(score) ? '' : ' (' + (score * 100).toFixed(0) + '%)'); + label = name + ((score * 100).toFixed(0) === 'NaN' || isNaN(score) ? '' : ' (' + (score * 100).toFixed(0) + '%)'); if ((Math.round(parseFloat(score) * 100) / 100) === 0) { - label = d.name; + label = name; } } @@ -315,12 +322,146 @@ class SunburstWidget { } getUniqueId(d, _class) { - var uid = this.id + '_' + d.id; + var uid = this.id + '_' + utils.getValue(d, 'id'); if (_class) { uid += '.' + _class; } return uid; } + + getColors(d) { + d.coloridx = (d.depth === 1 || d.depth === 0) ? utils.getValue(d, 'id') : d.parent.coloridx; + + if (d.coloridx === 'personality') { + return { + innerRingDarkColor: this.COLOR_PALLETTE.traits_dark, + innerRingLightColor: this.COLOR_PALLETTE.traits_light, + outerRingColor: this.COLOR_PALLETTE.facet + }; + } else if (d.coloridx === 'needs') { + return { + innerRingDarkColor: this.COLOR_PALLETTE.needs_dark, + innerRingLightColor: this.COLOR_PALLETTE.needs_light, + outerRingColor: this.COLOR_PALLETTE.need + }; + } else if (d.coloridx === 'values') { + return { + innerRingDarkColor: this.COLOR_PALLETTE.values_dark, + innerRingLightColor: this.COLOR_PALLETTE.values_light, + outerRingColor: this.COLOR_PALLETTE.value + }; + } else { + return { + innerRingDarkColor: this.COLOR_PALLETTE.traits_dark, + innerRingLightColor: this.COLOR_PALLETTE.traits_light, + outerRingColor: this.COLOR_PALLETTE.facet + }; + } + } + + createParts(g, d) { + var self = this; + var uid; + var colors = this.getColors(d); + var arc1color = d.depth < 2 ? this.d3.color(colors.innerRingLightColor) : this.d3.color(colors.innerRingDarkColor); + var strokecolor = arc1color; + var bottom = utils.isLocatedBottom(d); + + if (!d.children) { + uid = this.getUniqueId(d, 'bar'); + if (!this._childElements.parts[uid]) { + this._childElements.parts[uid] = g.append('path') + .attr('class', '_bar') + .style('stroke', '#EEE') + .style('fill', this.d3.color(colors.outerRingColor)); + } + + uid = this.getUniqueId(d, 'sector_leaf_text'); + if (!this._childElements.parts[uid]) { + this._childElements.parts[uid] = g.append('text') + .attr('class', 'sector_leaf_text'); + } + } else { + uid = this.getUniqueId(d, 'arc1'); + if (!this._childElements.parts[uid]) { + this._childElements.parts[uid] = g.append('path') + .attr('class', 'arc1') + .style('stroke', strokecolor) + .style('fill', arc1color); + } + + uid = this.getUniqueId(d, 'arc2'); + if (!this._childElements.parts[uid]) { + this._childElements.parts[uid] = g.append('path') + .attr('class', 'arc2') + .style('stroke', strokecolor) + .style('fill', arc1color) + .style('fill-opacity', 0.15); + } + + uid = this.getUniqueId(d, 'arc_for_label'); + if (!this._childElements.parts[uid]) { + this._childElements.parts[uid] = g.append('path') + .attr('class', 'arc_for_label') + .attr('id', function(d) { + return self.getUniqueId(d, 'arc_for_label'); + }) + .style('stroke-opacity', 0) + .style('fill-opacity', 0); + } + + uid = this.getUniqueId(d, 'sector_label_path'); + if (!this._childElements.parts[uid]) { + this._childElements.parts[uid] = g.append('text') + .attr('class', 'sector_label') + .attr('visibility', function(d) { + return d.depth === 1 ? 'visible' : null; + }) + .attr('class', 'sector_nonleaf_text') + .append('textPath') + .attr('class', 'sector_label_path') + .attr('position-in-sector', d.depth <= 1 ? 'center' : (bottom ? 'inner' : 'outer')) // Since both text lines share the same 'd', this class annotation tells where is the text, helping to determine the real arc length + .attr('font-size', function(d) { + return 30 / Math.sqrt(d.depth + 1); + }) + .attr('xlink:href', function(d) { + return '#' + self.getUniqueId(d, 'arc_for_label'); + }); + } + + if (d.depth > 1) { + uid = this.getUniqueId(d, 'arc_for_label_number'); + if (!this._childElements.parts[uid]) { + this._childElements.parts[uid] = g.append('path') + .attr('class', 'arc_for_label_number') + .attr('id', function(d) { + return self.getUniqueId(d, 'arc_for_label_number'); + }) + .style('stroke-opacity', 0) + .style('fill-opacity', 0); + } + + uid = this.getUniqueId(d, 'sector_label_number_path'); + if (!this._childElements.parts[uid]) { + this._childElements.parts[uid] = g.append('text') + .attr('class', 'sector_label_number ') + .attr('visibility', function(d) { + return d.depth === 1 ? 'visible' : null; + }) + .attr('class', 'sector_nonleaf_text') + .append('textPath') + .attr('class', 'sector_label_number_path') + .attr('position-in-sector', bottom ? 'outer' : 'inner') // Since both text lines share the same 'd', this class annotation tells where is the text, helping to determine the real arc length + .attr('font-size', function() { + return 10; + }) + .attr('xlink:href', function(d) { + return '#' + self.getUniqueId(d, 'arc_for_label_number'); + }); + } + } + } + } } module.exports = SunburstWidget;