Skip to content

Commit 681693d

Browse files
committed
feat(heatmap): gradient legend + color-mapping fixes
Adds an opt-in continuous gradient legend for heatmaps that replaces the categorical legend with a color strip and a hover-tracking arrow. Honors legend.position (top/right/bottom/left) and a new `align` config (start/center/end). Strip length accepts px or percent (default '70%'). Stops are either derived from `colorScale.ranges` or sampled from the same shade function the cells use. Also fixes two pre-existing color-mapping bugs in the shared treemap/heatmap helper: - `determineColor` produced wildly negative percentages when min === max (the `total - 0.000001` fallback went negative); now short-circuits to 0. - `highlightRangeInSeries` hover used `val < range.to` while `determineColor` colored cells with `val <= range.to`, so boundary values were colored by one range but highlighted by another. Both now use inclusive bounds.
1 parent 2838f65 commit 681693d

10 files changed

Lines changed: 1303 additions & 25 deletions

File tree

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
7+
<title>HeatMap with Gradient Legend</title>
8+
9+
<link href="../../assets/styles.css" rel="stylesheet" />
10+
11+
<style>
12+
#chart {
13+
max-width: 650px;
14+
margin: 35px auto;
15+
}
16+
</style>
17+
18+
19+
<script src="https://cdn.jsdelivr.net/npm/react@17.0.2/umd/react.production.min.js"></script>
20+
<script src="https://cdn.jsdelivr.net/npm/react-dom@17.0.2/umd/react-dom.production.min.js"></script>
21+
<script src="https://cdn.jsdelivr.net/npm/prop-types@15.8.1/prop-types.min.js"></script>
22+
<script src="https://cdn.jsdelivr.net/npm/babel-core@5.8.34/browser.min.js"></script>
23+
<script src="../../../dist/apexcharts.js"></script>
24+
<script src="https://cdn.jsdelivr.net/npm/react-apexcharts@1.7.0/dist/react-apexcharts.iife.min.js"></script>
25+
26+
27+
<script>
28+
// Replace Math.random() with a pseudo-random number generator to get reproducible results in e2e tests
29+
// Based on https://gist.github.com/blixt/f17b47c62508be59987b
30+
var _seed = 42
31+
Math.random = function () {
32+
_seed = (_seed * 16807) % 2147483647
33+
return (_seed - 1) / 2147483646
34+
}
35+
</script>
36+
37+
<script>
38+
function generateData(count, yrange) {
39+
var i = 0
40+
var series = []
41+
while (i < count) {
42+
var x = (i + 1).toString()
43+
var y =
44+
Math.floor(Math.random() * (yrange.max - yrange.min + 1)) + yrange.min
45+
46+
series.push({
47+
x: x,
48+
y: y,
49+
})
50+
i++
51+
}
52+
return series
53+
}
54+
</script>
55+
</head>
56+
57+
<body>
58+
59+
<div id="app"></div>
60+
61+
<div id="html">&lt;div id=&quot;chart&quot;&gt;
62+
&lt;ReactApexChart options={state.options} series={state.series} type=&quot;heatmap&quot; height={380} /&gt;
63+
&lt;/div&gt;</div>
64+
65+
<script type="text/babel">
66+
const ApexChart = () => {
67+
const [state, setState] = React.useState({
68+
series: [
69+
{
70+
name: 'Mon',
71+
data: generateData(18, { min: 0, max: 90 }),
72+
},
73+
{
74+
name: 'Tue',
75+
data: generateData(18, { min: 0, max: 90 }),
76+
},
77+
{
78+
name: 'Wed',
79+
data: generateData(18, { min: 0, max: 90 }),
80+
},
81+
{
82+
name: 'Thu',
83+
data: generateData(18, { min: 0, max: 90 }),
84+
},
85+
{
86+
name: 'Fri',
87+
data: generateData(18, { min: 0, max: 90 }),
88+
},
89+
{
90+
name: 'Sat',
91+
data: generateData(18, { min: 0, max: 90 }),
92+
},
93+
{
94+
name: 'Sun',
95+
data: generateData(18, { min: 0, max: 90 }),
96+
},
97+
],
98+
options: {
99+
chart: {
100+
height: 380,
101+
type: 'heatmap',
102+
},
103+
plotOptions: {
104+
heatmap: {
105+
shadeIntensity: 0.5,
106+
radius: 2,
107+
useFillColorAsStroke: false,
108+
colorScale: {
109+
// Continuous gradient legend replaces the default categorical legend.
110+
// The arrow tracks the hovered cell's value along the spectrum and
111+
// automatically reorients based on legend.position.
112+
gradientLegend: {
113+
enabled: true,
114+
width: 240,
115+
thickness: 14,
116+
showHoverValue: true,
117+
},
118+
},
119+
},
120+
},
121+
colors: ['#008FFB'],
122+
dataLabels: {
123+
enabled: false,
124+
},
125+
stroke: {
126+
width: 1,
127+
},
128+
legend: {
129+
position: 'bottom',
130+
},
131+
title: {
132+
text: 'Temperature Heatmap — hover a cell to see its value on the gradient',
133+
},
134+
},
135+
})
136+
137+
return (
138+
<div>
139+
<div id="chart">
140+
<ReactApexChart
141+
options={state.options}
142+
series={state.series}
143+
type="heatmap"
144+
height={380}
145+
/>
146+
</div>
147+
<div id="html-dist"></div>
148+
</div>
149+
)
150+
}
151+
152+
const domContainer = document.querySelector('#app')
153+
ReactDOM.render(<ApexChart />, domContainer)
154+
</script>
155+
156+
157+
</body>
158+
</html>
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<title>HeatMap with Gradient Legend</title>
2+
3+
<scripts>
4+
<script>
5+
function generateData(count, yrange) {
6+
var i = 0;
7+
var series = [];
8+
while (i < count) {
9+
var x = (i + 1).toString();
10+
var y = Math.floor(Math.random() * (yrange.max - yrange.min + 1)) + yrange.min;
11+
12+
series.push({
13+
x: x,
14+
y: y
15+
});
16+
i++;
17+
}
18+
return series;
19+
}
20+
</script>
21+
</scripts>
22+
23+
<chart>
24+
<options>
25+
chart: {
26+
height: 380,
27+
type: 'heatmap',
28+
},
29+
plotOptions: {
30+
heatmap: {
31+
shadeIntensity: 0.5,
32+
radius: 2,
33+
useFillColorAsStroke: false,
34+
colorScale: {
35+
// Continuous gradient legend replaces the default categorical legend.
36+
// The arrow tracks the hovered cell's value along the spectrum and
37+
// automatically reorients based on legend.position.
38+
gradientLegend: {
39+
enabled: true,
40+
width: 240,
41+
thickness: 14,
42+
showHoverValue: true,
43+
}
44+
}
45+
}
46+
},
47+
colors: ['#008FFB'],
48+
dataLabels: {
49+
enabled: false
50+
},
51+
stroke: {
52+
width: 1
53+
},
54+
legend: {
55+
position: 'bottom'
56+
},
57+
title: {
58+
text: 'Temperature Heatmap — hover a cell to see its value on the gradient'
59+
},
60+
</options>
61+
62+
<series>
63+
[{
64+
name: 'Mon',
65+
data: generateData(18, { min: 0, max: 90 })
66+
},
67+
{
68+
name: 'Tue',
69+
data: generateData(18, { min: 0, max: 90 })
70+
},
71+
{
72+
name: 'Wed',
73+
data: generateData(18, { min: 0, max: 90 })
74+
},
75+
{
76+
name: 'Thu',
77+
data: generateData(18, { min: 0, max: 90 })
78+
},
79+
{
80+
name: 'Fri',
81+
data: generateData(18, { min: 0, max: 90 })
82+
},
83+
{
84+
name: 'Sat',
85+
data: generateData(18, { min: 0, max: 90 })
86+
},
87+
{
88+
name: 'Sun',
89+
data: generateData(18, { min: 0, max: 90 })
90+
}
91+
]
92+
</series>
93+
</chart>
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
7+
<title>HeatMap with Gradient Legend</title>
8+
9+
<link href="../../assets/styles.css" rel="stylesheet" />
10+
11+
<style>
12+
#chart {
13+
max-width: 650px;
14+
margin: 35px auto;
15+
}
16+
</style>
17+
18+
19+
<script src="../../../dist/apexcharts.js"></script>
20+
21+
22+
<script>
23+
// Replace Math.random() with a pseudo-random number generator to get reproducible results in e2e tests
24+
// Based on https://gist.github.com/blixt/f17b47c62508be59987b
25+
var _seed = 42
26+
Math.random = function () {
27+
_seed = (_seed * 16807) % 2147483647
28+
return (_seed - 1) / 2147483646
29+
}
30+
</script>
31+
32+
<script>
33+
function generateData(count, yrange) {
34+
var i = 0
35+
var series = []
36+
while (i < count) {
37+
var x = (i + 1).toString()
38+
var y =
39+
Math.floor(Math.random() * (yrange.max - yrange.min + 1)) + yrange.min
40+
41+
series.push({
42+
x: x,
43+
y: y,
44+
})
45+
i++
46+
}
47+
return series
48+
}
49+
</script>
50+
</head>
51+
52+
<body>
53+
<div id="chart"></div>
54+
55+
<script>
56+
var options = {
57+
series: [
58+
{
59+
name: 'Mon',
60+
data: generateData(18, { min: 0, max: 90 }),
61+
},
62+
{
63+
name: 'Tue',
64+
data: generateData(18, { min: 0, max: 90 }),
65+
},
66+
{
67+
name: 'Wed',
68+
data: generateData(18, { min: 0, max: 90 }),
69+
},
70+
{
71+
name: 'Thu',
72+
data: generateData(18, { min: 0, max: 90 }),
73+
},
74+
{
75+
name: 'Fri',
76+
data: generateData(18, { min: 0, max: 90 }),
77+
},
78+
{
79+
name: 'Sat',
80+
data: generateData(18, { min: 0, max: 90 }),
81+
},
82+
{
83+
name: 'Sun',
84+
data: generateData(18, { min: 0, max: 90 }),
85+
},
86+
],
87+
chart: {
88+
height: 380,
89+
type: 'heatmap',
90+
},
91+
plotOptions: {
92+
heatmap: {
93+
shadeIntensity: 0.5,
94+
radius: 2,
95+
useFillColorAsStroke: false,
96+
colorScale: {
97+
// Continuous gradient legend replaces the default categorical legend.
98+
// The arrow tracks the hovered cell's value along the spectrum and
99+
// automatically reorients based on legend.position.
100+
gradientLegend: {
101+
enabled: true,
102+
width: 240,
103+
thickness: 14,
104+
showHoverValue: true,
105+
},
106+
},
107+
},
108+
},
109+
colors: ['#008FFB'],
110+
dataLabels: {
111+
enabled: false,
112+
},
113+
stroke: {
114+
width: 1,
115+
},
116+
legend: {
117+
position: 'bottom',
118+
},
119+
title: {
120+
text: 'Temperature Heatmap — hover a cell to see its value on the gradient',
121+
},
122+
}
123+
124+
var chart = new ApexCharts(document.querySelector('#chart'), options)
125+
chart.render()
126+
</script>
127+
128+
129+
</body>
130+
</html>

0 commit comments

Comments
 (0)