ui-modules/app-inspector/app/components/task-sunburst/task-sunburst.util.js (148 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import * as d3 from "d3"; import MODULE_NAME from "./task-sunburst.directive"; export const COLOR_MODES = [ 'simple', 'multi' ]; export const DEFAULT_COLOR_MODE = COLOR_MODES[0]; export function isNewEntity(d) { return d.data.task && d.parent && d.parent.data.task && d.data.task.entityId && d.data.task.entityId != d.parent.data.task.entityId; } export function isInProgress(d) { var t = findTask(d); return (t.startTimeUtc && !t.endTimeUtc); } export function taskClasses(d, classes) { if (!classes) classes = []; if (typeof classes === "string") classes = [ classes ]; if (!d) return classes; if (!d.id) return taskClasses(d.data || d.task, classes); classes.push("task"); if (!d.startTimeUtc) classes.push("unstarted"); else { classes.push("started"); if (!d.endTimeUtc) classes.push("in-progress"); else classes.push("completed"); } return classes; } export function orderFn(d1, d2) { function orderFnHelper(x1, x2) { if (x1 && x2) return x2 - x1; // if no value, it comes _after_ if (x2) return -1; if (x1) return 1; return 0; } // use start time if avail var dd1 = d1['data'] || d1; var t1 = dd1['task'] || {}; var dd2 = d2['data'] || d2; var t2 = dd2['task'] || {}; var result = orderFnHelper(t1['startTimeUtc'], t2['startTimeUtc']); if (result) return result; result = orderFnHelper(t1['submitTimeUtc'], t2['submitTimeUtc']); if (result) return result; result = orderFnHelper(t1['endTimeUtc'], t2['endTimeUtc']); if (result) return result; return orderFnHelper(dd1['sequenceId'], dd2['sequenceId']); } var colors; colors = { ERROR: d3.color(__BRAND_DANGER__ || "#B43"), NORMAL_COMPLETE: d3.color(__BRAND_COMPLETE__ || "#007600").darker(1), ACTIVE_NORMAL: d3.color(__BRAND_SUCCESS__ || "#090"), PALETTES: {} }; { // build a palette to use, avoiding anything that looks like success // (danger we don't avoid as it is usually bright) var palettes = COLOR_MODES; for(var p of palettes) { var hueToAvoid = d3.hsl(colors.ACTIVE_NORMAL).h; var palette = []; var simpleColors = true; var i = 0; while (i<360) { if ((i-hueToAvoid+360)%360 < 45 || (i-hueToAvoid+360)%360>360-45) { // skip hues near the hue to avoid } else { // palette.push(d3.hsl(i, 0.6, 0.2)); palette.push((p==="simple")? colors.NORMAL_COMPLETE : d3.hsl(i, 0.6, 0.2)); } // hue change more significant for colours at bottom of palette so larger increment above 96 if (i>=96) i+=12; i+=6; } colors.PALETTES[p] = palette; } } Object.assign(colors, colors, { ACTIVE_DARK: colors.ACTIVE_NORMAL.darker(0.4), ACTIVE_BRIGHT: colors.ACTIVE_NORMAL.brighter(0.2), scale: function(x, scheme) { const palette = colors.PALETTES[scheme||DEFAULT_COLOR_MODE]; return palette[((hash(x) % palette.length)+palette.length)%palette.length]; }, nodeToUseForHue: function(x) { return x; // one trick is for completed leaf nodes to all get their parent's colour to make it look tidier // return (findTask(x.parent).id && !x.children) ? x.parent : x; }, nodeName: function(x) { return (x.data && x.data.name) || x.displayName || x; }, succeededFn: function(x, scheme) { return colors.scale(colors.nodeName(x), scheme); }, unstartedFn: function(x) { var base = d3.hsl(colors.succeededFn(x)); base.s = 0.3; base.l = 0.9; return base; }, f: function(x, scheme) { var t = findTask(x); if (t.isError) return colors.ERROR; if (t.endTimeUtc) return colors.succeededFn(colors.nodeToUseForHue(x), scheme).toString(); if (t.startTimeUtc) return colors.ACTIVE_NORMAL; return colors.unstartedFn(colors.nodeToUseForHue(x)).toString(); } }); colors.ACTIVE_ANIMATE_VALUES = [ colors.ACTIVE_NORMAL, colors.ACTIVE_DARK, colors.ACTIVE_NORMAL, colors.ACTIVE_BRIGHT, colors.ACTIVE_NORMAL].join(";"); // just in case someone wants to set the colors function setColors(newColors) { colors = newColors; } export function colors() { return colors; } export function findTask(x) { if (!x) return {}; if (x.id) return x; if (x.task) return x.task; if (x.data) return findTask(x.data); return {}; } export function hash(x) { if (!x) return 0; if (typeof x !== "string") return hash(x.toString()); var result = 0, i; if (x.length === 0) return result; for (i = 0; i < x.length; i++) { result = ((result << 5) - result) + x.charCodeAt(i) + 8765; result |= 0; // Convert to 32bit integer } return result; } /* returns a function which generates an arc for a d3 data object */ export function arcF(options) { if (!options || !options.scaling || !options.visible_arc_length) { throw "At minimum, scaling and visible_arc_length options are required"; } // if using for tweening options.t = options.t==null ? 1 : options.t; options.visible_arc_start_fn = options.visible_arc_start_fn || (x => 0); // whether the arc should be really thin, or normal width options.isMinimal = options.isMinimal || false; options.visible_arc_length = options.visible_arc_length; // if tweening can also use this options.old_visible_arc_length = options.old_visible_arc_length || options.visible_arc_length; return d3.arc() .startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, options.scaling.fx(d.x0))) * ( options.old_visible_arc_length * (1-options.t) + options.visible_arc_length * options.t ) + options.visible_arc_start_fn( options.old_visible_arc_length ) * 2 * Math.PI * (1-options.t) + options.visible_arc_start_fn( options.visible_arc_length ) * 2 * Math.PI * options.t; }) .endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, options.scaling.fx(d.x0 + options.t*(d.x1-d.x0)))) * ( options.old_visible_arc_length * (1-options.t) + options.visible_arc_length * options.t ) + options.visible_arc_start_fn( options.old_visible_arc_length ) * 2 * Math.PI * (1-options.t) + options.visible_arc_start_fn( options.visible_arc_length ) * 2 * Math.PI * options.t; }) .innerRadius(function(d) { return options.scaling.fy(d.depth-1); }) .outerRadius(function(d) { return options.isMinimal ? options.scaling.fy(d.depth-1)+1.5 : options.scaling.fy(d.depth); }); } export function taskId(d) { return findTask(d).id; } export function id(x) { return x; } const STORAGE_KEY_COLOR_MODE = 'brooklyn.'+MODULE_NAME+'.color_mode'; export function getSunburstColorMode($window) { return $window.localStorage.getItem(STORAGE_KEY_COLOR_MODE) || DEFAULT_COLOR_MODE; } export function toggleSunburstColorMode($window) { const m = getSunburstColorMode($window); $window.localStorage.setItem(STORAGE_KEY_COLOR_MODE, COLOR_MODES[ (COLOR_MODES.findIndex(x => x == m) + 1) % COLOR_MODES.length ]); }