Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/650.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added a light/dark theme toggle to the flame graph and table HTML reports, which respects the system color scheme and remembers the user's choice.
60 changes: 55 additions & 5 deletions src/memray/reporters/assets/common.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,53 @@
import _ from "lodash";

// Build Plotly layout overrides for the current theme (light or dark).
// `kind` selects the variant: "main" for the full chart, "small" for the
// mini chart that sits inside the dark navbar.
export function getPlotlyTheme(kind) {
const isDark =
document.documentElement.getAttribute("data-theme") === "dark";
if (isDark) {
const paper = "#15181d";
const plot = kind === "small" ? "#07090b" : "#111418";
const fg = "#e6e6e6";
const grid = "#2a2f36";
return {
paper_bgcolor: paper,
plot_bgcolor: plot,
font: { color: fg },
xaxis: { gridcolor: grid, zerolinecolor: grid, linecolor: grid },
yaxis: { gridcolor: grid, zerolinecolor: grid, linecolor: grid },
legend: { font: { color: fg } },
};
}
// Light theme: keep the original look. The mini chart inside the dark
// navbar uses #343a40 to blend with bg-dark.
return {
paper_bgcolor: "#ffffff",
plot_bgcolor: kind === "small" ? "#343a40" : "#ffffff",
font: { color: "#212529" },
xaxis: { gridcolor: "#e9ecef", zerolinecolor: "#e9ecef", linecolor: "#e9ecef" },
yaxis: { gridcolor: "#e9ecef", zerolinecolor: "#e9ecef", linecolor: "#e9ecef" },
legend: { font: { color: "#212529" } },
};
}

// Re-apply the theme to all currently rendered Plotly charts. Safe to call
// even if a given chart hasn't been created yet.
export function applyPlotlyTheme() {
const targets = [
{ id: "memoryGraph", kind: "main" },
{ id: "smallMemoryGraph", kind: "small" },
{ id: "plot", kind: "main" },
];
targets.forEach(({ id, kind }) => {
const el = document.getElementById(id);
if (el && el.data) {
Plotly.relayout(id, getPlotlyTheme(kind));
}
});
}

export function initMemoryGraph(memory_records) {
const time = memory_records.map((a) => new Date(a[0]));
const resident_size = memory_records.map((a) => a[1]);
Expand All @@ -22,7 +70,10 @@ export function initMemoryGraph(memory_records) {

var data = [resident_size_plot, heap_size_plot];

var layout = {
const mainTheme = getPlotlyTheme("main");
const smallTheme = getPlotlyTheme("small");

var layout = _.merge({}, mainTheme, {
xaxis: {
title: {
text: "Time",
Expand All @@ -36,9 +87,9 @@ export function initMemoryGraph(memory_records) {
exponentformat: "B",
ticksuffix: "B",
},
};
});

var layout_small = {
var layout_small = _.merge({}, smallTheme, {
height: 40,
margin: {
l: 0,
Expand All @@ -47,14 +98,13 @@ export function initMemoryGraph(memory_records) {
t: 0,
pad: 4,
},
plot_bgcolor: "#343a40", // this matches the color of bg-dark in our navbar
yaxis: {
tickformat: ".4~s",
exponentformat: "B",
ticksuffix: "B",
},
showlegend: false,
};
});
var config = {
responsive: true,
displayModeBar: true,
Expand Down
2 changes: 2 additions & 0 deletions src/memray/reporters/assets/flamegraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import {
onResize,
onInvert,
getFlamegraph,
applyFlamegraphTheme,
} from "./flamegraph_common";

window.resizeMemoryGraph = resizeMemoryGraph;
window.memrayApplyFlamegraphTheme = applyFlamegraphTheme;

function packedDataToTree(packedData) {
const { strings, nodes, unique_threads } = packedData;
Expand Down
28 changes: 24 additions & 4 deletions src/memray/reporters/assets/flamegraph_common.js
Original file line number Diff line number Diff line change
Expand Up @@ -299,13 +299,18 @@ function fileExtension(filename) {
);
}

function isDarkTheme() {
return document.documentElement.getAttribute("data-theme") === "dark";
}

function colorByExtension(extension) {
const dark = isDarkTheme();
if (extension == "py") {
return d3.schemePastel1[2];
return dark ? "#4f8a5e" : d3.schemePastel1[2];
} else if (extension == "c" || extension == "cpp" || extension == "h") {
return d3.schemePastel1[5];
return dark ? "#9a8442" : d3.schemePastel1[5];
} else {
return d3.schemePastel1[8];
return dark ? "#5a5f68" : d3.schemePastel1[8];
}
}

Expand All @@ -316,12 +321,27 @@ function memrayColorMapper(d, originalColor) {
}
// "builtin" / nodes that we don't want to highlight
if (!d.data.name || !d.data.location) {
return "#EEE";
return isDarkTheme() ? "#2a2f36" : "#EEE";
}

return colorByExtension(fileExtension(d.data.location[1]));
}

// Re-apply fill colors on all existing flame graph rects without forcing a
// full re-render. Called by the theme toggle so dark/light switches live.
export function applyFlamegraphTheme() {
if (typeof d3 === "undefined") return;
d3.selectAll("#chart svg g").each(function () {
const sel = d3.select(this);
const d = sel.datum();
if (!d) return;
const rect = sel.select("rect");
if (!rect.empty()) {
rect.attr("fill", memrayColorMapper(d));
}
});
}

// Show the 'Threads' dropdown if we have thread data, and populate it
export function initThreadsDropdown(data, merge_threads) {
if (merge_threads === true) {
Expand Down
10 changes: 7 additions & 3 deletions src/memray/reporters/assets/temporal_flamegraph.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { debounced } from "./common";
import _ from "lodash";
import { debounced, getPlotlyTheme } from "./common";

import {
initThreadsDropdown,
Expand All @@ -12,8 +13,11 @@ import {
onInvert,
getFilteredChart,
getFlamegraph,
applyFlamegraphTheme,
} from "./flamegraph_common";

window.memrayApplyFlamegraphTheme = applyFlamegraphTheme;

var active_plot = null;
var current_dimensions = null;

Expand Down Expand Up @@ -268,7 +272,7 @@ function initMemoryGraph(memory_records) {
responsive: true,
displayModeBar: false,
};
var layout = {
var layout = _.merge({}, getPlotlyTheme("main"), {
xaxis: {
title: {
text: "Time",
Expand All @@ -285,7 +289,7 @@ function initMemoryGraph(memory_records) {
exponentformat: "B",
ticksuffix: "B",
},
};
});

Plotly.newPlot("plot", plot_data, layout, config).then((plot) => {
console.assert(active_plot === null);
Expand Down
2 changes: 1 addition & 1 deletion src/memray/reporters/templates/assets/flamegraph.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/memray/reporters/templates/assets/flamegraph_common.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/memray/reporters/templates/assets/table.js

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Loading
Loading