javascript - D3 Sunburst: Determine Leaf Color by Data and have its Parents Inherit that Color -
so new d3 visualizations , have run roadblock in learning. have pretty basic sunburst set running off data determines if test had "failure" or "success". failure or success represented outer ring of sunburst data's result.
i'm trying change outer ring's color based on success or failure. example if test result success, color green , if failure turn red. feel there way i'm overlooking.
also once color has been determined, want it's parent arcs, way innermost ring, "inherit" color , red have higher priority. if 1 group of test has failure in it, arc represents group red well.
prtg's sunburst works in manner , cant find connection between 2 implement functionality.
i'm using basic sunburst code d3's examples:
define(function(require, exports, module) { var _ = require('underscore'); var simplesplunkview = require("splunkjs/mvc/simplesplunkview"); var nester = require("../underscore-nest/underscore-nest"); var d3 = require("../d3/d3"); require("css!./sunburst.css"); window.nester = nester; var animation_duration = 750; // milliseconds var sunburst = simplesplunkview.extend({ moduleid: module.id, classname: "splunk-toolkit-sunburst", options: { managerid: null, data: 'preview', charttitle: null, valuefield: null, categoryfields: null, truncatevalue: 0, formatlabel: _.identity, formattooltip: function(d) { return (d.name || "total") + ": " + d.value; } }, output_mode: "json_rows", initialize: function() { simplesplunkview.prototype.initialize.apply(this, arguments); this.settings.on("change:valuefield", this.render, this); this.settings.on("change:categoryfields", this.render, this); this.settings.on("change:formatlabel change:formattooltip change:charttitle", this.render, this); // set resize callback. $(window).resize(_.debounce(_.bind(this._handleresize, this), 20)); }, _handleresize: function() { this.render(); }, createview: function() { // here wet initial view layout var margin = {top: 30, right: 30, bottom: 30, left: 30}; var availablewidth = parseint(this.settings.get("width") || this.$el.width()); var availableheight = parseint(this.settings.get("height") || this.$el.height()); this.$el.html(""); var svg = d3.select(this.el) .append("svg") .attr("width", availablewidth) .attr("height", availableheight) .attr("pointer-events", "all"); // returned object gets passed updateview viz return { container: this.$el, svg: svg, margin: margin}; }, // making data how want updateview job formatdata: function(data) { var valuefield = this.settings.get('valuefield'); var rawfields = this.resultsmodel.data().fields; var fieldlist = this.settings.get("categoryfields"); if(fieldlist){ fieldlist = fieldlist.split(/[ ,]+/); } else{ fieldlist = this.resultsmodel.data().fields; } var objects = _.map(data, function(row) { return _.object(rawfields, row); }); var dataresults = nester.nest(objects, fieldlist, function(children) { var total = 0; _.each(children, function(child){ var size = child[valuefield] || 1; total += size; }); return total; }); dataresults['name'] = this.settings.get("charttitle") || ""; data = { 'results': dataresults, 'fields': fieldlist }; return data; }, updateview: function(viz, data) { var = this; var formatlabel = this.settings.get("formatlabel") || _.identity; var formattooltip = this.settings.get("formattooltip") || function(d) { return d.name; }; var truncatevalue = this.settings.get("truncatevalue"); var containerheight = this.$el.height(); var containerwidth = this.$el.width(); // clear svg var svg = $(viz.svg[0]); svg.empty(); svg.height(containerheight); svg.width(containerwidth); // add graph group child of main svg var graphwidth = containerwidth - viz.margin.left - viz.margin.right; var graphheight = containerheight - viz.margin.top - viz.margin.bottom; var graph = viz.svg .append("g") .attr("width", graphwidth) .attr("height", graphheight) .attr("transform", "translate(" + ((graphwidth/2) + viz.margin.left ) + "," + ((graphheight/2) + viz.margin.top ) + ")"); var radius = math.min(graphwidth, graphheight) / 2; var color = d3.scale.category20().range(["#98df8a"]); var colora = d3.scale.category20().range(["#98df8a"]); //green var colorb = d3.scale.category20().range(["#d62728"]); //red var colorc = d3.scale.category20().range(["#bcbd22"]); //yellow var x = d3.scale.linear() .range([0, 2 * math.pi]); var y = d3.scale.linear() .range([0, radius]); var partition = d3.layout.partition() .value(function(d) { return d['value']; }); var arc = d3.svg.arc() .startangle(function(d) { return math.max(0, math.min(2 * math.pi, x(d.x))); }) .endangle(function(d) { return math.max(0, math.min(2 * math.pi, x(d.x + d.dx))); }) .innerradius(function(d) { return math.max(0, y(d.y)); }) .outerradius(function(d) { return math.max(0, y(d.y + d.dy)); }); var root = data.results; var g = graph.selectall("g") .data(partition.nodes(root)) .enter().append("g"); var leaves = d3.selectall("rect").filter(function(d) { return d.children === null; }); var path = g.append("path") .attr("d", arc) .style("fill", function(d) {return color((d.children ? d : d.parent).name); }) .on("click", click); path.append("title") .text(formattooltip); var textanchorpos = function(depthmarker) { return function(d) { return (d.depth === depthmarker) ? 'middle' : ((x(d.x + d.dx / 2) > math.pi) ? "end" : "start"); }; }; var texttransform = function(depthmarker) { return function(d) { // objects @ origin don't need rotated var angle = x(d.x + d.dx / 2) * 180 / math.pi + (d.depth === depthmarker ? 0 : -90); // objects @ origin don't need moved. // "5" pads text off drawn circle. var translation = d.depth === depthmarker ? 0 : (y(d.y) + 5); var rotate = angle; return "rotate(" + rotate + ")translate(" + (translation) + ")rotate(" + (angle > 90 ? -180 : 0) + ")"; }; }; var text = g.append("text") .attr("text-anchor", textanchorpos(0)) .attr("transform", texttransform(0)) .attr("dy", ".2em") .attr("x", 0) .text(function(d) { var slicewidth = math.abs(math.max(0, y(d.y)) - math.max(0, y(d.y + d.dy))); var formatted = formatlabel(d.name); // trunctate title return formatted.substring(0, slicewidth / truncatevalue); }) .on("click", click); text.append("title") .text(formattooltip); function click(d) { // "at depth" object treated differently; // centered , not rotated. var depthmarker = d.depth; // fade out text elements text.transition().attr("opacity", 0); path.transition() .duration(animation_duration) .attrtween("d", arctween(d)) .each("end", function(e, i) { // check if animated element's data e lies // within visible angle span given in d , // element d or possible child of d if ((e.x >= d.x && e.x < (d.x + d.dx)) && (e.depth >= d.depth)) { // selection of associated text element var arctext = d3.select(this.parentnode).select("text"); // fade in text element , recalculate positions arctext.transition().duration(animation_duration) .attr("opacity", 1) .attr("text-anchor", textanchorpos(depthmarker)) .attr("transform", texttransform(depthmarker)) .attr("dy", ".2em") .attr("x", 0); } }); } // interpolate scales! function arctween(d) { var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]), yd = d3.interpolate(y.domain(), [d.y, 1]), yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]); return function(d, i) { return ? function(t) { return arc(d); } : function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); }; }; } } }); return sunburst;
});
the coloring happens here:
var path = g.append("path") .attr("d", arc) .style("fill", function(d) {return color((d.children ? d : d.parent).name); }) .on("click", click);
so might able examine test-state of element get's passed anonymous function (in style call) , return 'green' or 'red'. might quite straight forward leaves, little trickier parents. since must discover looking @ parent element if has children failed tests return 'red', else return 'green'.
Comments
Post a Comment