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 ]);
}