Skip to content

Commit

Permalink
Merge pull request #55 from bric3/break-color-mapping
Browse files Browse the repository at this point in the history
refactor(flamegraph)!: Color function now takes a framebox
  • Loading branch information
bric3 authored Apr 14, 2022
2 parents d903d95 + 8b06b01 commit 7f8aa07
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import java.awt.event.ActionListener;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -188,7 +187,7 @@ public void setStacktraceTreeModel(StacktraceTreeModel stacktraceTreeModel) {

private Consumer<FlameGraph<Node>> dataApplier(StacktraceTreeModel stacktraceTreeModel) {
var flatFrameList = JfrFrameNodeConverter.convert(stacktraceTreeModel);
return (flameGraph) -> flameGraph.setData(
return (flameGraph) -> flameGraph.setConfigurationAndData(
flatFrameList,
NodeDisplayStringProvider.of(
(frame) -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/
package io.github.bric3.fireplace;

import io.github.bric3.fireplace.flamegraph.FrameBox;
import org.openjdk.jmc.common.IMCFrame.Type;
import org.openjdk.jmc.flightrecorder.stacktrace.tree.Node;

Expand All @@ -24,7 +25,7 @@ public enum JfrFrameColorMode {
final Pattern runtimePrefixes = Pattern.compile("(java\\.|javax\\.|sun\\.|com\\.sun\\.|com\\.oracle\\.|com\\.ibm\\.)");

@Override
public Color getColor(Function<Object, Color> colorMapper, Node frameNode) {
public Color getJfrNodeColor(Function<Object, Color> colorMapper, Node frameNode) {
if (frameNode.isRoot()) {
return rootNodeColor;
}
Expand All @@ -42,7 +43,7 @@ public Color getColor(Function<Object, Color> colorMapper, Node frameNode) {
},
BY_MODULE {
@Override
public Color getColor(Function<Object, Color> colorMapper, Node frameNode) {
public Color getJfrNodeColor(Function<Object, Color> colorMapper, Node frameNode) {
if (frameNode.isRoot()) {
return rootNodeColor;
}
Expand All @@ -51,7 +52,7 @@ public Color getColor(Function<Object, Color> colorMapper, Node frameNode) {
},
BY_FRAME_TYPE {
@Override
public Color getColor(Function<Object, Color> colorMapper, Node frameNode) {
public Color getJfrNodeColor(Function<Object, Color> colorMapper, Node frameNode) {
if (frameNode.isRoot()) {
return rootNodeColor;
}
Expand All @@ -76,9 +77,9 @@ public Color getColor(Function<Object, Color> colorMapper, Node frameNode) {
public static Color inlinedColor = Color.pink;
public static Color interpretedColor = Color.orange;

protected abstract Color getColor(Function<Object, Color> colorMapper, Node frameNode);
protected abstract Color getJfrNodeColor(Function<Object, Color> colorMapper, Node frameNode);

public Function<Node, Color> colorMapperUsing(Function<Object, Color> colorMapper) {
return frameNode -> getColor(colorMapper, frameNode);
public Function<FrameBox<Node>, Color> colorMapperUsing(Function<Object, Color> colorMapper) {
return frameNode -> getJfrNodeColor(colorMapper, frameNode.actualNode);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,17 @@ public class Colors {
private static volatile boolean darkMode = false;

/**
* Perceived brightness threshold between dark and light.
* Perceived brightness threshold between dark and light (between 0 and 255).
*
* <p>
* Between 0 and 255
* Subtract 0.05 from 0.5, because WCAG (Web Content Accessibility Guidelines)
* contrast ratio is defined as (L1 + 0.05) / (L2 + 0.05), where L1 and L2
* are brightness scores. 0.05 stands for minimum brightness of the screen
* which is never truly black due to highlighting.
* See <a href="https://www.w3.org/TR/WCAG21/#dfn-relative-luminance">WCAG 2.1</a>
* </p>
*/
public static final int DARK_PERCEIVED_BRIGHTNESS_THRESHOLD = 128;
public static final int DARK_PERCEIVED_BRIGHTNESS_THRESHOLD = gammaFunction(0.45);

/** Color BLACK with alpha 0xD0. */
public static Color translucent_black_D0 = new Color(0xD0000000, true);
Expand Down Expand Up @@ -269,22 +275,34 @@ public Color[] colors() {
* Pick a foreground color based on the perceived luminance of the
* background color and on the dark mode.
*
* <p>
* Assumes an sRGB color space.
* </p>
*
* @param backgroundColor The background color.
* @return The foreground color.
*/
public static Color foregroundColor(Color backgroundColor) {
// sRGB luminance(Y) values
var brightness = brightness(backgroundColor);

return brightness < DARK_PERCEIVED_BRIGHTNESS_THRESHOLD ?
Color.white :
darkMode ? Colors.panelBackground : Colors.panelForeground;
var isBright = brightness >= DARK_PERCEIVED_BRIGHTNESS_THRESHOLD;
return isBright ?
(darkMode ? Colors.panelBackground : Colors.panelForeground) :
Color.white;
}

/**
* Computes the perceived brightness of a color
* <p>
* See <a href="https://stackoverflow.com/questions/596216/formula-to-determine-perceived-brightness-of-rgb-color">Perceived brightness on StackOverflow</a>
* </p>
*
* <p>
* Assuming the given color is in the sRGB color space, it translates colors
* into linear-lighting RGB color space, undoing the gamma encoding of sRGB space.
* Then it multiplies each channel with a coefficient to take into account human perception of brightness.
* Also see <a href="https://www.w3.org/TR/WCAG21/#dfn-relative-luminance">W3C doc</a>
* </p>
*
* @param color The color
* @return The perceived brightness between 0 and 255
Expand All @@ -311,7 +329,7 @@ private static int gammaFunction(double v) {

private static double inverseOfGammaFunction(int ic) {
double c = ic / 255.0;
if (c <= 0.04045) {
if (c <= 0.03928) {
return c / 12.92;
} else {
return Math.pow((c + 0.055) / 1.055, 2.4);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public static JLayer<JScrollPane> create(Supplier<JScrollPane> scrollPaneSupplie
private static class ScrollBackToTopLayerUI extends LayerUI<JScrollPane> {
public int xGap;
public int yGap;
private final Container buttonContainer = new JPanel();
private final JPanel buttonContainer = new JPanel();
private final Point currentMousePoint = new Point();
private final JButton button = new JButton(new UpArrowIcon(new Color(0xAA_3D_42_44, true),
new Color(0xAA_38_9F_D6, true))) {
Expand Down Expand Up @@ -204,7 +204,7 @@ public void paintIcon(Component c, Graphics g, int x, int y) {
if (c instanceof AbstractButton && ((AbstractButton) c).getModel().isRollover()) {
g2.setPaint(rolloverColor);
} else {
g2.setPaint(UIManager.getColor("Button.borderColor"));
g2.setPaint(UIManager.getColor("Button.foreground"));
}

float w2 = getIconWidth() / 2f;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ public interface ColorMapper<T> extends Function<T, Color> {
* @return A color.
*/
static ColorMapper<Object> ofObjectHashUsing(Color... palette) {
return value -> value == null ?
return o -> o == null ?
palette[0] :
palette[Math.abs(Objects.hashCode(value)) % palette.length];
palette[Math.abs(Objects.hashCode(o)) % palette.length];
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,10 @@ public interface HoveringListener<T> {
public FlameGraph() {
canvas = new FlameGraphCanvas<>();
listener = new FlameGraphMouseInputListener<>(canvas);
component = JScrollPaneWithButton.create(
var scrollPane = new JScrollPane(canvas);
var layeredScrollPane = JScrollPaneWithButton.create(
() -> {
var scrollPane = new JScrollPane(canvas);

// Code to tweak the actions
// https://stackoverflow.com/a/71009104/48136
// see javax.swing.plaf.basic.BasicScrollPaneUI.Actions
Expand All @@ -132,6 +133,38 @@ public FlameGraph() {
return scrollPane;
}
);

component = wrap(scrollPane, layeredScrollPane);
}

/**
* Murky workaround to propagate the background color to the canvas
* since JLayer is final.
*/
private JPanel wrap(JScrollPane scrollPane, JLayer<JScrollPane> layeredScrollPane) {
var wrapper = new JPanel(new BorderLayout()) {
@Override
public void updateUI() {
super.updateUI();
scrollPane.setBorder(null);
scrollPane.getVerticalScrollBar().setBackground(getBackground());
scrollPane.getHorizontalScrollBar().setBackground(getBackground());
canvas.setBackground(getBackground());
}

@Override
public void setBackground(Color bg) {
super.setBackground(bg);
scrollPane.setBorder(null);
scrollPane.setBackground(bg);
scrollPane.getVerticalScrollBar().setBackground(bg);
scrollPane.getHorizontalScrollBar().setBackground(bg);
canvas.setBackground(bg);
}
};
wrapper.setBorder(null);
wrapper.add(layeredScrollPane);
return wrapper;
}

/**
Expand All @@ -146,7 +179,7 @@ public void configureCanvas(Consumer<JComponent> canvasConfigurer) {
*
* @param frameColorFunction A function that takes a frame and returns a color.
*/
public void setColorFunction(Function<T, Color> frameColorFunction) {
public void setColorFunction(Function<FrameBox<T>, Color> frameColorFunction) {
Objects.requireNonNull(frameColorFunction);
this.canvas.getFlameGraphPainter()
.ifPresent(fgp -> fgp.frameColorFunction = frameColorFunction);
Expand Down Expand Up @@ -229,15 +262,52 @@ public void setHoveringListener(HoveringListener<T> hoverListener) {
* <li>The tooltip text from the current node</li>
* </ul>
*
* @param frames The {@code FrameBox} list to display.
* @param frameToString function to display label in frames.
* @param frameColorFunction the frame to background color function.
* @param tooltipTextFunction the frame tooltip text function.
* @param frames The {@code FrameBox} list to display.
* @param frameToString function to display label in frames.
* @param frameColorFunction the frame to background color function.
* @param tooltipTextFunction the frame tooltip text function.
*/
@Deprecated(forRemoval = true)
public void setData(List<FrameBox<T>> frames,
NodeDisplayStringProvider<T> frameToString,
Function<T, Color> frameColorFunction,
Function<FrameBox<T>, String> tooltipTextFunction) {
setConfigurationAndData(
frames,
frameToString,
frame -> frameColorFunction.apply(frame.actualNode),
tooltipTextFunction
);
}

/**
* Actually set the {@link FlameGraph} with typed data and configure how to use it.
* <p>
* It takes a list of {@link FrameBox} objects that wraps the actual data,
* which is referred to as <em>node</em>.
* </p>
* <p>
* In particular this function defines the behavior to access the typed data:
* <ul>
* <li>Possible string candidates to display in frames, those are
* selected based on the available space</li>
* <li>The root node text to display, if something specific is relevant,
* like the type of events, their number, etc.</li>
* <li>The frame background color, this function can be replaced by
* {@link #setColorFunction(Function)}, note that the foreground color
* is chosen automatically</li>
* <li>The tooltip text from the current node</li>
* </ul>
*
* @param frames The {@code FrameBox} list to display.
* @param frameToString function to display label in frames.
* @param frameColorFunction the frame to background color function.
* @param tooltipTextFunction the frame tooltip text function.
*/
public void setConfigurationAndData(List<FrameBox<T>> frames,
NodeDisplayStringProvider<T> frameToString,
Function<FrameBox<T>, Color> frameColorFunction,
Function<FrameBox<T>, String> tooltipTextFunction) {
var flameGraphPainter = new FlameGraphPainter<>(
Objects.requireNonNull(frames),
Objects.requireNonNull(frameToString),
Expand Down Expand Up @@ -340,7 +410,7 @@ public void highlightFrames(Set<FrameBox<T>> framesToHighlight, String searched)
Objects.requireNonNull(framesToHighlight);
Objects.requireNonNull(searched);
canvas.getFlameGraphPainter().ifPresent(painter ->
painter.setHighlightFrames(framesToHighlight, searched)
painter.setHighlightFrames(framesToHighlight, searched)
);
canvas.repaint();
}
Expand Down Expand Up @@ -559,9 +629,9 @@ public FlameGraphCanvas(FlameGraphPainter<T> flameGraphPainter) {
@Override
public void updateUI() {
super.updateUI();
if (flameGraphPainter != null) {
flameGraphPainter.updateUI();
}
// if (flameGraphPainter != null) {
// flameGraphPainter.updateUI();
// }
}

@Override
Expand Down
Loading

0 comments on commit 7f8aa07

Please sign in to comment.