Skip to content

Commit

Permalink
Support style hierarchy and composition in StyleProvider
Browse files Browse the repository at this point in the history
The OutputElementStyle's values map properties can now override the
properties defined for the style identified by the parent key.

A multi-level hierarchy of parent styles is supported. Style composition
by using a comma-delimited list of parent keys is supported.

The 'background-color', 'height', 'border-style', 'border-color' and
'border-width' properties are supported for time graph states.

The 'symbol-type', 'color', 'height' and 'vertical-align' properties are
supported for time graph annotations.

The 'opacity' property and '-blend' modifier are supported for color
styles.

The '-factor' modifier is supported for number styles.

Signed-off-by: Patrick Tasse <patrick.tasse@ericsson.com>
  • Loading branch information
PatrickTasse committed May 3, 2021
1 parent 4ad1e06 commit 2da8235
Show file tree
Hide file tree
Showing 3 changed files with 227 additions and 43 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const StyleProperties = {
BACKGROUND_COLOR: 'background-color',
BLEND: '-blend',
BORDER_STYLE: 'border-style',
BORDER_COLOR: 'border-color',
BORDER_WIDTH: 'border-width',
COLOR: 'color',
FACTOR: '-factor',
HEIGHT: 'height',
OPACITY: 'opacity',
SYMBOL_TYPE: 'symbol-type',
VERTICAL_ALIGN: 'vertical-align'
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { TspClient } from 'tsp-typescript-client/lib/protocol/tsp-client';
import { QueryHelper } from 'tsp-typescript-client/lib/models/query/query-helper';
import { OutputStyleModel } from 'tsp-typescript-client/lib/models/styles';
import { OutputStyleModel, OutputElementStyle } from 'tsp-typescript-client/lib/models/styles';
import { StyleProperties } from './style-properties';

export class StyleProvider {
private tspClient: TspClient;
Expand Down Expand Up @@ -98,4 +99,177 @@ export class StyleProvider {
const styles = this.tmpStyleObject[this.outputId];
return styles ? styles : {};
}

/**
* Get the style property value for the specified element style. The style
* hierarchy is traversed until a value is found.
*
* @param elementStyle
* the style
* @param property
* the style property
* @return the style property value, or undefined
*/
public getStyle(elementStyle: OutputElementStyle, property: string): any | undefined {
let style: OutputElementStyle | undefined = elementStyle;
const styleQueue: string[] = [];
while (style !== undefined) {
const styleValues = style.values;
const value = styleValues[property];
if (value) {
return value;
}

// Get the next style
style = this.popNextStyle(style, styleQueue);
}
return undefined;
}

/**
* Get the style property number value for the specified element style. The
* style hierarchy is traversed until a number value is found, and the
* returned number value will be multiplied by the first
* StyleProperties.FACTOR suffixed modifier style that was found
* along the way, if any.
*
* @param elementStyle
* the style
* @param property
* the style property
* @return the style property number value, or undefined
*/
public getNumberStyle(elementStyle: OutputElementStyle, property: string): number | undefined {
let factor = undefined;
let style: OutputElementStyle | undefined = elementStyle;
const styleQueue: string[] = [];
while (style) {
const styleValues = style.values;
if (factor === undefined) {
const factorValue = styleValues[property + StyleProperties.FACTOR];
if (typeof factorValue === 'number') {
factor = factorValue as number;
}
}
const value = styleValues[property];
if (typeof value === 'number') {
const numberValue = value as number;
return (factor === undefined) ? numberValue : factor * numberValue;
}

// Get the next style
style = this.popNextStyle(style, styleQueue);
}
return factor;
}

/**
* Get the style property color value for the specified element style. The
* style hierarchy is traversed until a color and opacity value is found,
* and the returned color value will be blended with the first
* StyleProperties.BLEND suffixed modifier style that was found
* along the way, if any.
*
* @param elementStyle
* the style
* @param property
* the style property
* @return the style property color value, or undefined
*/
public getColorStyle(elementStyle: OutputElementStyle, property: string): { color: number, alpha: number } | undefined {
let color: string | undefined = undefined;
let opacity: number | undefined = undefined;
let blend = undefined;
let style: OutputElementStyle | undefined = elementStyle;
const styleQueue: string[] = [];
while (style) {
const styleValues = style.values;
if (blend === undefined) {
const blendValue = styleValues[property + StyleProperties.BLEND];
if (typeof blendValue === 'string') {
blend = this.rgbaStringToColor(blendValue as string);
}
}
if (opacity === undefined) {
const opacityValue = styleValues[StyleProperties.OPACITY];
if (typeof opacityValue === 'number') {
opacity = opacityValue as number;
if (color) {
break;
}
}
}
if (color === undefined) {
const value = styleValues[property];
if (typeof value === 'string') {
color = value as string;
if (opacity) {
break;
}
}
}

// Get the next style
style = this.popNextStyle(style, styleQueue);
}
const alpha = (opacity === undefined) ? 1.0 : opacity;
const rgba = (color === undefined) ? (opacity === undefined ? undefined : this.rgbaToColor(0, 0, 0, alpha)) : this.rgbStringToColor(color, alpha);
return (rgba === undefined) ? undefined : (blend === undefined) ? rgba : this.blend(rgba, blend);
}

private popNextStyle(style: OutputElementStyle, styleQueue: string[]): OutputElementStyle | undefined {
// Get the next style
let nextStyle = undefined;
const parentKey = style.parentKey;
if (parentKey) {
const split = parentKey.split(',');
split.forEach(styleKey => styleQueue.push(styleKey));
}
while (nextStyle === undefined && styleQueue.length !== 0) {
const nextKey = styleQueue.pop();
if (nextKey) {
nextStyle = this.styleModel?.styles[nextKey];
}
}

return nextStyle;
}

private blend(color1: { color: number, alpha: number }, color2: { color: number, alpha: number }): { color: number, alpha: number } {
/**
* If a color component 'c' with alpha 'a' is blended with color
* component 'd' with alpha 'b', the blended color and alpha are:
*
* <pre>
* color = (a*(1-b)*c + b*d) / (a + b - a*b)
* alpha = (a + b - a*b)
* </pre>
*/
const alpha = color1.alpha + color2.alpha - color1.alpha * color2.alpha;
const r = this.blendComponent(color1.alpha, (color1.color >> 16) & 0xff, color2.alpha, (color2.color >> 16) & 0xff, alpha);
const g = this.blendComponent(color1.alpha, (color1.color >> 8) & 0xff, color2.alpha, (color2.color >> 8) & 0xff, alpha);
const b = this.blendComponent(color1.alpha, (color1.color) & 0xff, color2.alpha, (color2.color) & 0xff, alpha);
return this.rgbaToColor(r, g, b, alpha);
}

private blendComponent(alpha1: number, color1: number, alpha2: number, color2: number, alpha: number): number {
return Math.floor((alpha1 * (1.0 - alpha2) * color1 + alpha2 * color2) / alpha);
}

private rgbStringToColor(rgbString: string, alpha: number): { color: number, alpha: number } {
const color = parseInt(rgbString.replace(/^#/, ''), 16);
return { color, alpha };
}

private rgbaStringToColor(rgbaString: string): { color: number, alpha: number } {
const int = parseInt(rgbaString.replace(/^#/, ''), 16);
const color = (int >> 8) & 0xffffff;
const alpha = (int & 0xff) / 255;
return { color, alpha };
}

private rgbaToColor(r: number, g: number, b: number, alpha: number): { color: number, alpha: number } {
const color = (r << 16) + (g << 8) + b;
return { color, alpha };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { TimeGraphEntry } from 'tsp-typescript-client/lib/models/timegraph';
import { signalManager, Signals } from '@trace-viewer/base/lib/signals/signal-manager';
import { AbstractOutputProps, AbstractOutputState } from './abstract-output-component';
import { AbstractTreeOutputComponent } from './abstract-tree-output-component';
import { StyleProperties } from './data-providers/style-properties';
import { StyleProvider } from './data-providers/style-provider';
import { TspDataProvider } from './data-providers/tsp-data-provider';
import { ReactTimeGraphContainer } from './utils/timegraph-container-component';
Expand Down Expand Up @@ -340,30 +341,36 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent<Timegr
const metadata = state.data;
if (metadata && metadata.style) {
const elementStyle: OutputElementStyle = metadata.style;
const modelStyle = styleModel.styles[elementStyle.parentKey];
if (modelStyle) {
const currentStyle = Object.assign({}, modelStyle.values, elementStyle.values);
if (currentStyle) {
const color = this.hexStringToNumber(currentStyle['background-color']);
let height = this.props.style.rowHeight * 0.8;
if (currentStyle['height']) {
height = currentStyle['height'] * height;
}
return {
color: color,
height: height,
borderWidth: state.selected ? 2 : 0,
borderColor: 0xeef20c
};
const backgroundColor = this.styleProvider.getColorStyle(elementStyle, StyleProperties.BACKGROUND_COLOR);
const heightFactor = this.styleProvider.getNumberStyle(elementStyle, StyleProperties.HEIGHT);
let height = this.props.style.rowHeight * 0.8;
if (heightFactor) {
height = heightFactor * height;
}
const borderStyle = this.styleProvider.getStyle(elementStyle, StyleProperties.BORDER_STYLE);
let borderColor = undefined;
let borderWidth = undefined;
if (borderStyle && borderStyle !== 'none') {
borderColor = this.styleProvider.getColorStyle(elementStyle, StyleProperties.BORDER_COLOR);
if (borderColor === undefined) {
borderColor = { color: 0x000000, alpha: 1 };
}
borderWidth = this.styleProvider.getNumberStyle(elementStyle, StyleProperties.BORDER_WIDTH);
if (borderWidth === undefined) {
borderWidth = 1;
}
}
return {
color: backgroundColor ? backgroundColor.color : 0x000000,
alpha: backgroundColor ? backgroundColor.alpha : 1.0,
height: height,
borderWidth: state.selected ? 2 : (borderWidth ? borderWidth : 0),
borderColor: state.selected ? 0xeef20c : (borderColor ? borderColor.color : 0x000000)
};
}
}
return this.getDefaultStateStyle(state);
}
private hexStringToNumber(hexString: string): number {
return parseInt(hexString.replace(/^#/, ''), 16);
}

private getDefaultStateStyle(state: TimelineChart.TimeGraphState) {
const styleProvider = new StyleProvider(this.props.outputDescriptor.id, this.props.traceId, this.props.tspClient);
Expand Down Expand Up @@ -450,31 +457,21 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent<Timegr
const metadata = annotation.data;
if (metadata && metadata.style) {
const elementStyle: OutputElementStyle = metadata.style;
const modelStyle = styleModel.styles[elementStyle.parentKey];
let currentStyle = Object.assign({}, elementStyle.values);
if (modelStyle) {
currentStyle = Object.assign({}, modelStyle.values, elementStyle.values);
}
if (currentStyle) {
let color = 0;
if (currentStyle['color']) {
color = this.hexStringToNumber(currentStyle['color']);
}
let symbolSize = this.props.style.rowHeight * 0.8 / 2;
if (currentStyle['height']) {
symbolSize = currentStyle['height'] * symbolSize;
}
let vAlign = 'center';
if (currentStyle['vertical-align']) {
vAlign = currentStyle['vertical-align'];
}
return {
symbol: currentStyle['symbol-type'],
size: symbolSize,
color: color,
verticalAlign: vAlign
};
const symbolType = this.styleProvider.getStyle(elementStyle, StyleProperties.SYMBOL_TYPE);
const color = this.styleProvider.getColorStyle(elementStyle, StyleProperties.COLOR);
const heightFactor = this.styleProvider.getNumberStyle(elementStyle, StyleProperties.HEIGHT);
let symbolSize = this.props.style.rowHeight * 0.8 / 2;
if (heightFactor) {
symbolSize = heightFactor * symbolSize;
}
const vAlign = this.styleProvider.getStyle(elementStyle, StyleProperties.VERTICAL_ALIGN);
return {
symbol: symbolType ? symbolType : 'none',
size: symbolSize,
color: color ? color.color : 0x000000,
alpha: color ? color.alpha : 1.0,
verticalAlign: vAlign ? vAlign : 'middle'
};
}
}
return undefined;
Expand Down

0 comments on commit 2da8235

Please sign in to comment.