Skip to content

Commit bed6511

Browse files
committed
feature #33535 [WebProfilerBundle] Assign automatic colors to custom 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
2 parents c403706 + 329a74f commit bed6511

File tree

3 files changed

+75
-65
lines changed

3 files changed

+75
-65
lines changed

src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.css.twig

-41
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,3 @@
1-
/* Variables */
2-
3-
.sf-profiler-timeline {
4-
--color-default: #777;
5-
--color-section: #999;
6-
--color-event-listener: #00B8F5;
7-
--color-template: #66CC00;
8-
--color-doctrine: #FF6633;
9-
--color-messenger-middleware: #BDB81E;
10-
--color-controller-argument-value-resolver: #8c5de6;
11-
}
12-
131
/* Legend */
142

153
.sf-profiler-timeline .legends .timeline-category {
@@ -31,14 +19,6 @@
3119
display: inline-block;
3220
}
3321

34-
.sf-profiler-timeline .legends .{{ classnames.default|raw }} { border-color: var(--color-default); }
35-
.sf-profiler-timeline .legends .{{ classnames.section|raw }} { border-color: var(--color-section); }
36-
.sf-profiler-timeline .legends .{{ classnames.event_listener|raw }} { border-color: var(--color-event-listener); }
37-
.sf-profiler-timeline .legends .{{ classnames.template|raw }} { border-color: var(--color-template); }
38-
.sf-profiler-timeline .legends .{{ classnames.doctrine|raw }} { border-color: var(--color-doctrine); }
39-
.sf-profiler-timeline .legends .{{ classnames['messenger.middleware']|raw }} { border-color: var(--color-messenger-middleware); }
40-
.sf-profiler-timeline .legends .{{ classnames['controller.argument_value_resolver']|raw }} { border-color: var(--color-controller-argument-value-resolver); }
41-
4222
.timeline-graph {
4323
margin: 1em 0;
4424
width: 100%;
@@ -82,24 +62,3 @@
8262
.timeline-graph .timeline-period {
8363
stroke-width: 0;
8464
}
85-
.timeline-graph .{{ classnames.default|raw }} .timeline-period {
86-
fill: var(--color-default);
87-
}
88-
.timeline-graph .{{ classnames.section|raw }} .timeline-period {
89-
fill: var(--color-section);
90-
}
91-
.timeline-graph .{{ classnames.event_listener|raw }} .timeline-period {
92-
fill: var(--color-event-listener);
93-
}
94-
.timeline-graph .{{ classnames.template|raw }} .timeline-period {
95-
fill: var(--color-template);
96-
}
97-
.timeline-graph .{{ classnames.doctrine|raw }} .timeline-period {
98-
fill: var(--color-doctrine);
99-
}
100-
.timeline-graph .{{ classnames['messenger.middleware']|raw }} .timeline-period {
101-
fill: var(--color-messenger-middleware);
102-
}
103-
.timeline-graph .{{ classnames['controller.argument_value_resolver']|raw }} .timeline-period {
104-
fill: var(--color-controller-argument-value-resolver);
105-
}

src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig

+8-15
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,6 @@
22

33
{% import _self as helper %}
44

5-
{% set classnames = {
6-
'default': 'timeline-category-default',
7-
'section': 'timeline-category-section',
8-
'event_listener': 'timeline-category-event-listener',
9-
'template': 'timeline-category-template',
10-
'doctrine': 'timeline-category-doctrine',
11-
'messenger.middleware': 'timeline-category-messenger-middleware',
12-
'controller.argument_value_resolver': 'timeline-category-controller-argument-value-resolver',
13-
} %}
14-
155
{% block toolbar %}
166
{% set has_time_events = collector.events|length > 0 %}
177
{% set total_time = has_time_events ? '%.0f'|format(collector.duration) : 'n/a' %}
@@ -128,7 +118,7 @@
128118
</h3>
129119
{% endif %}
130120

131-
{{ helper.display_timeline(token, classnames, collector.events, collector.events.__section__.origin) }}
121+
{{ helper.display_timeline(token, collector.events, collector.events.__section__.origin) }}
132122

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

145-
{{ helper.display_timeline(child.token, classnames, events, collector.events.__section__.origin) }}
135+
{{ helper.display_timeline(child.token, events, collector.events.__section__.origin) }}
146136
{% endfor %}
147137
{% endif %}
148138

@@ -154,7 +144,7 @@
154144
</defs>
155145
</svg>
156146
<style type="text/css">
157-
{% include '@WebProfiler/Collector/time.css.twig' with classnames %}
147+
{% include '@WebProfiler/Collector/time.css.twig' %}
158148
</style>
159149
<script>
160150
{% include '@WebProfiler/Collector/time.js' %}
@@ -202,16 +192,19 @@
202192
{% endautoescape %}
203193
{% endmacro %}
204194

205-
{% macro display_timeline(token, classnames, events, origin) %}
195+
{% macro display_timeline(token, events, origin) %}
206196
{% import _self as helper %}
207197
<div class="sf-profiler-timeline">
208198
<div id="legend-{{ token }}" class="legends"></div>
209199
<svg id="timeline-{{ token }}" class="timeline-graph"></svg>
210200
<script>{% autoescape 'js' %}
211201
window.addEventListener('load', function onLoad() {
202+
const theme = new Theme();
203+
212204
new TimelineEngine(
205+
theme,
213206
new SvgRenderer(document.getElementById('timeline-{{ token }}')),
214-
new Legend(document.getElementById('legend-{{ token }}'), {{ classnames|json_encode|raw }}),
207+
new Legend(document.getElementById('legend-{{ token }}'), theme),
215208
document.getElementById('threshold'),
216209
{{ helper.dump_request_data(token, events, origin) }}
217210
);

src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.js

+67-9
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22

33
class TimelineEngine {
44
/**
5+
* @param {Theme} theme
56
* @param {Renderer} renderer
67
* @param {Legend} legend
78
* @param {Element} threshold
89
* @param {Object} request
910
* @param {Number} eventHeight
1011
* @param {Number} horizontalMargin
1112
*/
12-
constructor(renderer, legend, threshold, request, eventHeight = 36, horizontalMargin = 10) {
13+
constructor(theme, renderer, legend, threshold, request, eventHeight = 36, horizontalMargin = 10) {
14+
this.theme = theme;
1315
this.renderer = renderer;
1416
this.legend = legend;
1517
this.threshold = threshold;
@@ -81,7 +83,7 @@ class TimelineEngine {
8183
const lines = periods.map(period => this.createPeriod(period, category));
8284
const label = this.createLabel(this.getShortName(name), duration, memory, periods[0]);
8385
const title = this.renderer.createTitle(name);
84-
const group = this.renderer.group([title, border, label].concat(lines), this.legend.getClassname(event.category));
86+
const group = this.renderer.group([title, border, label].concat(lines), this.theme.getCategoryColor(event.category));
8587

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

@@ -100,7 +102,7 @@ class TimelineEngine {
100102
}
101103

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

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

215217
class Legend {
216-
constructor(element, classnames) {
218+
constructor(element, theme) {
217219
this.element = element;
218-
this.classnames = classnames;
220+
this.theme = theme;
219221

220222
this.toggle = this.toggle.bind(this);
221223
this.createCategory = this.createCategory.bind(this);
222224

223-
this.categories = Array.from(Object.keys(classnames)).map(this.createCategory);
225+
this.categories = Array.from(this.theme.getDefaultCategories()).map(this.createCategory);
224226
}
225227

226228
add(category) {
@@ -229,8 +231,8 @@ class Legend {
229231

230232
createCategory(category) {
231233
const element = document.createElement('button');
232-
233-
element.className = `timeline-category ${this.getClassname(category)} active`;
234+
element.className = `timeline-category active`;
235+
element.style.borderColor = this.theme.getCategoryColor(category);
234236
element.innerText = category;
235237
element.value = category;
236238
element.type = 'button';
@@ -390,13 +392,17 @@ class SvgRenderer {
390392
return element;
391393
}
392394

393-
createPath(path = null, className = null) {
395+
createPath(path = null, className = null, color = null) {
394396
const element = this.create('path', className);
395397

396398
if (path) {
397399
element.setAttribute('d', path);
398400
}
399401

402+
if (color) {
403+
element.setAttribute('fill', color);
404+
}
405+
400406
return element;
401407
}
402408

@@ -410,3 +416,55 @@ class SvgRenderer {
410416
return element;
411417
}
412418
}
419+
420+
class Theme {
421+
constructor(element) {
422+
this.reservedCategoryColors = {
423+
'default': '#777',
424+
'section': '#999',
425+
'event_listener': '#00b8f5',
426+
'template': '#66cc00',
427+
'doctrine': '#ff6633',
428+
'messenger_middleware': '#bdb81e',
429+
'controller.argument_value_resolver': '#8c5de6',
430+
};
431+
432+
this.customCategoryColors = [
433+
'#dbab09', // dark yellow
434+
'#ea4aaa', // pink
435+
'#964b00', // brown
436+
'#22863a', // dark green
437+
'#0366d6', // dark blue
438+
'#17a2b8', // teal
439+
];
440+
441+
this.getCategoryColor = this.getCategoryColor.bind(this);
442+
this.getDefaultCategories = this.getDefaultCategories.bind(this);
443+
}
444+
445+
getDefaultCategories() {
446+
return Object.keys(this.reservedCategoryColors);
447+
}
448+
449+
getCategoryColor(category) {
450+
return this.reservedCategoryColors[category] || this.getRandomColor(category);
451+
}
452+
453+
getRandomColor(category) {
454+
// instead of pure randomness, colors are assigned deterministically based on the
455+
// category name, to ensure that each custom category always displays the same color
456+
return this.customCategoryColors[this.hash(category) % this.customCategoryColors.length];
457+
}
458+
459+
// copied from https://github.com/darkskyapp/string-hash
460+
hash(string) {
461+
var hash = 5381;
462+
var i = string.length;
463+
464+
while(i) {
465+
hash = (hash * 33) ^ string.charCodeAt(--i);
466+
}
467+
468+
return hash >>> 0;
469+
}
470+
}

0 commit comments

Comments
 (0)