Skip to content

Commit

Permalink
feature #33535 [WebProfilerBundle] Assign automatic colors to custom …
Browse files Browse the repository at this point in the history
…Stopwatch categories (javiereguiluz)

This PR was merged into the 4.4 branch.

Discussion
----------

[WebProfilerBundle] Assign automatic colors to custom Stopwatch categories

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #33514
| License       | MIT
| Doc PR        | not needed

### Before

![image](https://user-images.githubusercontent.com/73419/64624345-d2907000-d3ea-11e9-9320-5b316768273d.png)

### After

![image](https://user-images.githubusercontent.com/73419/64624358-d6bc8d80-d3ea-11e9-875d-99396782d95a.png)

- - - - -

I'd appreciate reviews from JavaScript experts. Thanks!

Commits
-------

329a74f [WebProfilerBundle] Assign automatic colors to custom Stopwatch categories
  • Loading branch information
fabpot committed Sep 10, 2019
2 parents c403706 + 329a74f commit bed6511
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 65 deletions.
@@ -1,15 +1,3 @@
/* Variables */

.sf-profiler-timeline {
--color-default: #777;
--color-section: #999;
--color-event-listener: #00B8F5;
--color-template: #66CC00;
--color-doctrine: #FF6633;
--color-messenger-middleware: #BDB81E;
--color-controller-argument-value-resolver: #8c5de6;
}

/* Legend */

.sf-profiler-timeline .legends .timeline-category {
Expand All @@ -31,14 +19,6 @@
display: inline-block;
}

.sf-profiler-timeline .legends .{{ classnames.default|raw }} { border-color: var(--color-default); }
.sf-profiler-timeline .legends .{{ classnames.section|raw }} { border-color: var(--color-section); }
.sf-profiler-timeline .legends .{{ classnames.event_listener|raw }} { border-color: var(--color-event-listener); }
.sf-profiler-timeline .legends .{{ classnames.template|raw }} { border-color: var(--color-template); }
.sf-profiler-timeline .legends .{{ classnames.doctrine|raw }} { border-color: var(--color-doctrine); }
.sf-profiler-timeline .legends .{{ classnames['messenger.middleware']|raw }} { border-color: var(--color-messenger-middleware); }
.sf-profiler-timeline .legends .{{ classnames['controller.argument_value_resolver']|raw }} { border-color: var(--color-controller-argument-value-resolver); }

.timeline-graph {
margin: 1em 0;
width: 100%;
Expand Down Expand Up @@ -82,24 +62,3 @@
.timeline-graph .timeline-period {
stroke-width: 0;
}
.timeline-graph .{{ classnames.default|raw }} .timeline-period {
fill: var(--color-default);
}
.timeline-graph .{{ classnames.section|raw }} .timeline-period {
fill: var(--color-section);
}
.timeline-graph .{{ classnames.event_listener|raw }} .timeline-period {
fill: var(--color-event-listener);
}
.timeline-graph .{{ classnames.template|raw }} .timeline-period {
fill: var(--color-template);
}
.timeline-graph .{{ classnames.doctrine|raw }} .timeline-period {
fill: var(--color-doctrine);
}
.timeline-graph .{{ classnames['messenger.middleware']|raw }} .timeline-period {
fill: var(--color-messenger-middleware);
}
.timeline-graph .{{ classnames['controller.argument_value_resolver']|raw }} .timeline-period {
fill: var(--color-controller-argument-value-resolver);
}
Expand Up @@ -2,16 +2,6 @@

{% import _self as helper %}

{% set classnames = {
'default': 'timeline-category-default',
'section': 'timeline-category-section',
'event_listener': 'timeline-category-event-listener',
'template': 'timeline-category-template',
'doctrine': 'timeline-category-doctrine',
'messenger.middleware': 'timeline-category-messenger-middleware',
'controller.argument_value_resolver': 'timeline-category-controller-argument-value-resolver',
} %}

{% block toolbar %}
{% set has_time_events = collector.events|length > 0 %}
{% set total_time = has_time_events ? '%.0f'|format(collector.duration) : 'n/a' %}
Expand Down Expand Up @@ -128,7 +118,7 @@
</h3>
{% endif %}

{{ helper.display_timeline(token, classnames, collector.events, collector.events.__section__.origin) }}
{{ helper.display_timeline(token, collector.events, collector.events.__section__.origin) }}

{% if profile.children|length %}
<p class="help">Note: sections with a striped background correspond to sub-requests.</p>
Expand All @@ -142,7 +132,7 @@
<small>{{ events.__section__.duration }} ms</small>
</h4>

{{ helper.display_timeline(child.token, classnames, events, collector.events.__section__.origin) }}
{{ helper.display_timeline(child.token, events, collector.events.__section__.origin) }}
{% endfor %}
{% endif %}

Expand All @@ -154,7 +144,7 @@
</defs>
</svg>
<style type="text/css">
{% include '@WebProfiler/Collector/time.css.twig' with classnames %}
{% include '@WebProfiler/Collector/time.css.twig' %}
</style>
<script>
{% include '@WebProfiler/Collector/time.js' %}
Expand Down Expand Up @@ -202,16 +192,19 @@
{% endautoescape %}
{% endmacro %}

{% macro display_timeline(token, classnames, events, origin) %}
{% macro display_timeline(token, events, origin) %}
{% import _self as helper %}
<div class="sf-profiler-timeline">
<div id="legend-{{ token }}" class="legends"></div>
<svg id="timeline-{{ token }}" class="timeline-graph"></svg>
<script>{% autoescape 'js' %}
window.addEventListener('load', function onLoad() {
const theme = new Theme();
new TimelineEngine(
theme,
new SvgRenderer(document.getElementById('timeline-{{ token }}')),
new Legend(document.getElementById('legend-{{ token }}'), {{ classnames|json_encode|raw }}),
new Legend(document.getElementById('legend-{{ token }}'), theme),
document.getElementById('threshold'),
{{ helper.dump_request_data(token, events, origin) }}
);
Expand Down
Expand Up @@ -2,14 +2,16 @@

class TimelineEngine {
/**
* @param {Theme} theme
* @param {Renderer} renderer
* @param {Legend} legend
* @param {Element} threshold
* @param {Object} request
* @param {Number} eventHeight
* @param {Number} horizontalMargin
*/
constructor(renderer, legend, threshold, request, eventHeight = 36, horizontalMargin = 10) {
constructor(theme, renderer, legend, threshold, request, eventHeight = 36, horizontalMargin = 10) {
this.theme = theme;
this.renderer = renderer;
this.legend = legend;
this.threshold = threshold;
Expand Down Expand Up @@ -81,7 +83,7 @@ class TimelineEngine {
const lines = periods.map(period => this.createPeriod(period, category));
const label = this.createLabel(this.getShortName(name), duration, memory, periods[0]);
const title = this.renderer.createTitle(name);
const group = this.renderer.group([title, border, label].concat(lines), this.legend.getClassname(event.category));
const group = this.renderer.group([title, border, label].concat(lines), this.theme.getCategoryColor(event.category));

event.elements = Object.assign(event.elements || {}, { group, label, border });

Expand All @@ -100,7 +102,7 @@ class TimelineEngine {
}

createPeriod(period, category) {
const timeline = this.renderer.createPath(null, 'timeline-period');
const timeline = this.renderer.createPath(null, 'timeline-period', this.theme.getCategoryColor(category));

period.draw = category === 'section' ? this.renderer.setSectionLine : this.renderer.setPeriodLine;
period.elements = Object.assign(period.elements || {}, { timeline });
Expand Down Expand Up @@ -213,14 +215,14 @@ class TimelineEngine {
}

class Legend {
constructor(element, classnames) {
constructor(element, theme) {
this.element = element;
this.classnames = classnames;
this.theme = theme;

this.toggle = this.toggle.bind(this);
this.createCategory = this.createCategory.bind(this);

this.categories = Array.from(Object.keys(classnames)).map(this.createCategory);
this.categories = Array.from(this.theme.getDefaultCategories()).map(this.createCategory);
}

add(category) {
Expand All @@ -229,8 +231,8 @@ class Legend {

createCategory(category) {
const element = document.createElement('button');

element.className = `timeline-category ${this.getClassname(category)} active`;
element.className = `timeline-category active`;
element.style.borderColor = this.theme.getCategoryColor(category);
element.innerText = category;
element.value = category;
element.type = 'button';
Expand Down Expand Up @@ -390,13 +392,17 @@ class SvgRenderer {
return element;
}

createPath(path = null, className = null) {
createPath(path = null, className = null, color = null) {
const element = this.create('path', className);

if (path) {
element.setAttribute('d', path);
}

if (color) {
element.setAttribute('fill', color);
}

return element;
}

Expand All @@ -410,3 +416,55 @@ class SvgRenderer {
return element;
}
}

class Theme {
constructor(element) {
this.reservedCategoryColors = {
'default': '#777',
'section': '#999',
'event_listener': '#00b8f5',
'template': '#66cc00',
'doctrine': '#ff6633',
'messenger_middleware': '#bdb81e',
'controller.argument_value_resolver': '#8c5de6',
};

this.customCategoryColors = [
'#dbab09', // dark yellow
'#ea4aaa', // pink
'#964b00', // brown
'#22863a', // dark green
'#0366d6', // dark blue
'#17a2b8', // teal
];

this.getCategoryColor = this.getCategoryColor.bind(this);
this.getDefaultCategories = this.getDefaultCategories.bind(this);
}

getDefaultCategories() {
return Object.keys(this.reservedCategoryColors);
}

getCategoryColor(category) {
return this.reservedCategoryColors[category] || this.getRandomColor(category);
}

getRandomColor(category) {
// instead of pure randomness, colors are assigned deterministically based on the
// category name, to ensure that each custom category always displays the same color
return this.customCategoryColors[this.hash(category) % this.customCategoryColors.length];
}

// copied from https://github.com/darkskyapp/string-hash
hash(string) {
var hash = 5381;
var i = string.length;

while(i) {
hash = (hash * 33) ^ string.charCodeAt(--i);
}

return hash >>> 0;
}
}

0 comments on commit bed6511

Please sign in to comment.