diff --git a/frontend/src/toolbar/elements/Heatmap.tsx b/frontend/src/toolbar/elements/Heatmap.tsx index ce7a58f02fd4c..75785d3f3a768 100644 --- a/frontend/src/toolbar/elements/Heatmap.tsx +++ b/frontend/src/toolbar/elements/Heatmap.tsx @@ -1,6 +1,6 @@ import heatmapsJs, { Heatmap as HeatmapJS } from 'heatmap.js' import { useValues } from 'kea' -import { MutableRefObject, useCallback, useEffect, useRef } from 'react' +import { MutableRefObject, useCallback, useEffect, useMemo, useRef } from 'react' import { heatmapLogic } from '~/toolbar/elements/heatmapLogic' @@ -49,10 +49,26 @@ function HeatmapMouseInfo({ } export function Heatmap(): JSX.Element | null { - const { heatmapJsData, heatmapEnabled, heatmapFilters, windowWidth, windowHeight } = useValues(heatmapLogic) + const { heatmapJsData, heatmapEnabled, heatmapFilters, windowWidth, windowHeight, heatmapColorPalette } = + useValues(heatmapLogic) const heatmapsJsRef = useRef>() const heatmapsJsContainerRef = useRef() + const heatmapJSColorGradient = useMemo((): Record => { + switch (heatmapColorPalette) { + case 'blue': + return { '.0': 'rgba(0, 0, 255, 0)', '.100': 'rgba(0, 0, 255, 1)' } + case 'green': + return { '.0': 'rgba(0, 255, 0, 0)', '.100': 'rgba(0, 255, 0, 1)' } + case 'red': + return { '.0': 'rgba(255, 0, 0, 0)', '.100': 'rgba(255, 0, 0, 1)' } + + default: + // Defaults taken from heatmap.js + return { '.25': 'rgb(0,0,255)', '0.55': 'rgb(0,255,0)', '0.85': 'yellow', '1.0': 'rgb(255,0,0)' } + } + }, [heatmapColorPalette]) + const updateHeatmapData = useCallback((): void => { try { heatmapsJsRef.current?.setData(heatmapJsData) @@ -69,6 +85,7 @@ export function Heatmap(): JSX.Element | null { heatmapsJsRef.current = heatmapsJs.create({ container, + gradient: heatmapJSColorGradient, }) updateHeatmapData() @@ -78,6 +95,17 @@ export function Heatmap(): JSX.Element | null { updateHeatmapData() }, [heatmapJsData]) + useEffect(() => { + if (!heatmapsJsContainerRef.current) { + return + } + + heatmapsJsRef.current?.configure({ + container: heatmapsJsContainerRef.current, + gradient: heatmapJSColorGradient, + }) + }, [heatmapJSColorGradient]) + if (!heatmapEnabled || !heatmapFilters.enabled || heatmapFilters.type === 'scrolldepth') { return null } diff --git a/frontend/src/toolbar/elements/ScrollDepth.tsx b/frontend/src/toolbar/elements/ScrollDepth.tsx index d81e6478aea17..9e929de52c745 100644 --- a/frontend/src/toolbar/elements/ScrollDepth.tsx +++ b/frontend/src/toolbar/elements/ScrollDepth.tsx @@ -1,3 +1,4 @@ +import clsx from 'clsx' import { useValues } from 'kea' import { heatmapLogic } from '~/toolbar/elements/heatmapLogic' @@ -7,7 +8,7 @@ import { useMousePosition } from './useMousePosition' function ScrollDepthMouseInfo(): JSX.Element | null { const { posthog } = useValues(toolbarConfigLogic) - const { heatmapElements, rawHeatmapLoading } = useValues(heatmapLogic) + const { heatmapElements, rawHeatmapLoading, shiftPressed } = useValues(heatmapLogic) const { y: mouseY } = useMousePosition() @@ -35,8 +36,13 @@ function ScrollDepthMouseInfo(): JSX.Element | null { transform: 'translateY(-50%)', }} > -
-
+
+
{rawHeatmapLoading ? ( <>Loading... ) : heatmapElements.length ? ( @@ -46,7 +52,7 @@ function ScrollDepthMouseInfo(): JSX.Element | null { )}
-
+
) } @@ -54,7 +60,8 @@ function ScrollDepthMouseInfo(): JSX.Element | null { export function ScrollDepth(): JSX.Element | null { const { posthog } = useValues(toolbarConfigLogic) - const { heatmapEnabled, heatmapFilters, heatmapElements, scrollDepthPosthogJsError } = useValues(heatmapLogic) + const { heatmapEnabled, heatmapFilters, heatmapElements, scrollDepthPosthogJsError, heatmapColorPalette } = + useValues(heatmapLogic) if (!heatmapEnabled || !heatmapFilters.enabled || heatmapFilters.type !== 'scrolldepth') { return null @@ -71,11 +78,32 @@ export function ScrollDepth(): JSX.Element | null { function color(count: number): string { const value = 1 - count / maxCount - const safeValue = Math.max(0, Math.min(1, value)) - const hue = Math.round(260 * safeValue) - // Return hsl color. You can adjust saturation and lightness to your liking - return `hsl(${hue}, 100%, 50%)` + if (heatmapColorPalette === 'default') { + const safeValue = Math.max(0, Math.min(1, value)) + const hue = Math.round(260 * safeValue) + + // Return hsl color. You can adjust saturation and lightness to your liking + return `hsl(${hue}, 100%, 50%)` + } + + const rgba = [0, 0, 0, count / maxCount] + + switch (heatmapColorPalette) { + case 'red': + rgba[0] = 255 + break + case 'green': + rgba[1] = 255 + break + case 'blue': + rgba[2] = 255 + break + default: + break + } + + return `rgba(${rgba.join(', ')})` } return ( diff --git a/frontend/src/toolbar/elements/heatmapLogic.ts b/frontend/src/toolbar/elements/heatmapLogic.ts index b3e586949f3f3..f6256a91844be 100644 --- a/frontend/src/toolbar/elements/heatmapLogic.ts +++ b/frontend/src/toolbar/elements/heatmapLogic.ts @@ -1,3 +1,4 @@ +import { LemonSelectOption } from '@posthog/lemon-ui' import { actions, afterMount, beforeUnmount, connect, kea, listeners, path, reducers, selectors } from 'kea' import { loaders } from 'kea-loaders' import { encodeParams } from 'kea-router' @@ -57,6 +58,13 @@ export type HeatmapJsData = { } export type HeatmapFixedPositionMode = 'fixed' | 'relative' | 'hidden' +export const HEATMAP_COLOR_PALETTE_OPTIONS: LemonSelectOption[] = [ + { value: 'default', label: 'Default (multicolor)' }, + { value: 'red', label: 'Red (monocolor)' }, + { value: 'green', label: 'Green (monocolor)' }, + { value: 'blue', label: 'Blue (monocolor)' }, +] + export const heatmapLogic = kea([ path(['toolbar', 'elements', 'heatmapLogic']), connect({ @@ -86,6 +94,7 @@ export const heatmapLogic = kea([ fetchHeatmapApi: (params: HeatmapRequestType) => ({ params }), setHeatmapScrollY: (scrollY: number) => ({ scrollY }), setHeatmapFixedPositionMode: (mode: HeatmapFixedPositionMode) => ({ mode }), + setHeatmapColorPalette: (Palette: string | null) => ({ Palette }), }), windowValues(() => ({ windowWidth: (window: Window) => window.innerWidth, @@ -153,6 +162,14 @@ export const heatmapLogic = kea([ setHeatmapFixedPositionMode: (_, { mode }) => mode, }, ], + + heatmapColorPalette: [ + 'default' as string | null, + { persist: true }, + { + setHeatmapColorPalette: (_, { Palette }) => Palette, + }, + ], }), loaders(({ values }) => ({ diff --git a/frontend/src/toolbar/stats/HeatmapToolbarMenu.tsx b/frontend/src/toolbar/stats/HeatmapToolbarMenu.tsx index f5c9a5ce8ac17..f07471eec2a58 100644 --- a/frontend/src/toolbar/stats/HeatmapToolbarMenu.tsx +++ b/frontend/src/toolbar/stats/HeatmapToolbarMenu.tsx @@ -1,5 +1,5 @@ import { IconInfo, IconMagicWand } from '@posthog/icons' -import { LemonLabel, LemonSegmentedButton, LemonTag } from '@posthog/lemon-ui' +import { LemonLabel, LemonSegmentedButton, LemonSelect, LemonTag } from '@posthog/lemon-ui' import { useActions, useValues } from 'kea' import { CUSTOM_OPTION_KEY } from 'lib/components/DateFilter/types' import { IconSync } from 'lib/lemon-ui/icons' @@ -15,7 +15,7 @@ import React, { useState } from 'react' import { ToolbarMenu } from '~/toolbar/bar/ToolbarMenu' import { elementsLogic } from '~/toolbar/elements/elementsLogic' -import { heatmapLogic } from '~/toolbar/elements/heatmapLogic' +import { HEATMAP_COLOR_PALETTE_OPTIONS, heatmapLogic } from '~/toolbar/elements/heatmapLogic' import { currentPageLogic } from '~/toolbar/stats/currentPageLogic' import { toolbarConfigLogic } from '../toolbarConfigLogic' @@ -143,6 +143,7 @@ export const HeatmapToolbarMenu = (): JSX.Element => { elementStatsLoading, clickmapsEnabled, heatmapFixedPositionMode, + heatmapColorPalette, } = useValues(heatmapLogic) const { setCommonFilters, @@ -151,6 +152,7 @@ export const HeatmapToolbarMenu = (): JSX.Element => { setMatchLinksByHref, toggleClickmapsEnabled, setHeatmapFixedPositionMode, + setHeatmapColorPalette, } = useActions(heatmapLogic) const { setHighlightElement, setSelectedElement } = useActions(elementsLogic) @@ -321,6 +323,15 @@ export const HeatmapToolbarMenu = (): JSX.Element => {
+ + + + {heatmapFilters.type !== 'scrolldepth' && (