12#ifndef PLOT_HEATMAP_HPP
13#define PLOT_HEATMAP_HPP
28#include "attributes.hpp"
39 template<
class T>
struct mat {
40 const T* p; std::size_t rows, cols;
41 T operator()(std::size_t i, std::size_t j)
const {
return p[i*cols + j]; }
42 const T* begin()
const {
return p; }
43 const T* end()
const {
return p + rows*cols; }
52 std::string label,href;
58 inline std::uint32_t gradient(
double t){
59 if( t < 0.0 ) t = 0.0;
else if( t > 1.0 ) t = 1.0;
61 static constexpr double blue[3] = { 0.0, 64.0, 255.0};
62 static constexpr double yellow[3] = {255.0, 255.0, 0.0};
63 static constexpr double red[3] = {220.0, 30.0, 30.0};
67 r = blue[0] + u*(yellow[0]-blue[0]);
68 g = blue[1] + u*(yellow[1]-blue[1]);
69 b = blue[2] + u*(yellow[2]-blue[2]);
71 double u = (t - 0.5) / 0.5;
72 r = yellow[0] + u*(red[0]-yellow[0]);
73 g = yellow[1] + u*(red[1]-yellow[1]);
74 b = yellow[2] + u*(red[2]-yellow[2]);
76 auto clamp8 = [](
double v)->std::uint32_t {
77 if( v < 0 ) v = 0;
else if( v > 255 ) v = 255;
78 return static_cast<std::uint32_t
>(v + 0.5);
80 return (clamp8(r) << 16) | (clamp8(g) << 8) | clamp8(b);
87 typename std::enable_if<std::is_integral<T>::value || std::is_enum<T>::value>
88 ::type heatmap(impl::canvas_t& canvas,
float x,
float y,
89 std::vector<float>& dx, std::vector<float>& dy,
float width,
float height,
90 const plot::mat<T>& data,
const plot::attribute::color_t& palette,
91 const std::array<std::uint32_t,3>& ) {
92 using attribute_t = plot::attribute::element_t;
93 attribute_t mock_attr, gr_attr;
95 std::vector<T> M(data.begin(), data.end());
96 std::sort(M.begin(), M.end());
97 auto last = std::unique(M.begin(), M.end());
98 M.erase(last, M.end());
100 canvas.group(
static_cast<std::size_t
>(x),
static_cast<std::size_t
>(y), mock_attr, [&]() ->
void {
101 for(
auto value : M ) {
102 gr_attr.color = palette[
static_cast<std::ptrdiff_t
>(value)];
103 canvas.group(std::size_t{0}, std::size_t{0}, gr_attr, [&]() ->
void {
104 for(std::size_t j = 0; j < data.cols; j++)
for( std::size_t i = 0; i < data.rows; i++)
105 if( data(i,j) == value )
106 canvas.rect( dx[j], dy[i], width, height, 1.5, 1.5, mock_attr);
113namespace plot::impl {
115 template <
typename T>
116 typename std::enable_if<std::is_integral<T>::value || std::is_enum<T>::value>
117 ::type heatmap(impl::canvas_t& canvas,
float x,
float y,
118 std::vector<float>& dx, std::vector<float>& dy,
float width,
float height,
119 const std::vector<plot::data::point_t<T>>& data,
const plot::attribute::color_t& palette,
120 const std::array<std::uint32_t,3>& ) {
121 using attribute_t = plot::attribute::element_t;
122 attribute_t rect_attr, gr_attr;
124 gr_attr.color.reset();
125 canvas.group(
static_cast<std::size_t
>(x),
static_cast<std::size_t
>(y), gr_attr, [&]() ->
void {
127 rect_attr.href = v.href; rect_attr.label = v.label; rect_attr.color = palette[v.value];
128 canvas.rect(dx[v.x], dy[v.y], width, height, 1.5, 1.5, rect_attr);
134namespace plot::impl {
138 template <
typename T>
139 typename std::enable_if<std::is_arithmetic<T>::value && !std::is_integral<T>::value>
140 ::type heatmap(impl::canvas_t& canvas,
float x,
float y,
141 std::vector<float>& dx, std::vector<float>& dy,
float width,
float height,
142 const plot::mat<T>& data,
const plot::attribute::color_t& ,
143 const std::array<std::uint32_t,3>& grad ) {
144 using attribute_t = plot::attribute::element_t;
145 if( data.rows == 0 || data.cols == 0 )
return;
147 double lo =
static_cast<double>(*data.begin());
149 for(
const T& v : data){
150 double d =
static_cast<double>(v);
154 const double span = (hi > lo) ? (hi - lo) : 1.0;
157 canvas.group(
static_cast<std::size_t
>(x),
static_cast<std::size_t
>(y), gr_attr, [&]() ->
void {
158 for(std::size_t i = 0; i < data.rows; i++)
159 for(std::size_t j = 0; j < data.cols; j++){
160 double value =
static_cast<double>(data(i,j));
161 double t = (value - lo) / span;
163 cell.color = plot::attribute::color_t{ impl::gradient3(grad, t) };
164 cell.label = std::format(
"{:.4g}", value);
165 canvas.rect( dx[j], dy[i], width, height, 1.5, 1.5, cell );
171namespace plot::impl {
176 template <
class T,
class... arg_t>
177 std::pair<std::size_t,std::size_t>
178 heatmap_render(impl::canvas_t& canvas,
const T& data, arg_t... args ) {
179 using title_t =
typename arg::tpos<tag::title_t, arg_t...>;
180 using footnote_t =
typename arg::tpos<tag::footnote_t, arg_t...>;
181 using legend_t =
typename arg::tpos<tag::legend_t, arg_t...>;
182 using axisx_t =
typename arg::tpos<tag::axis::x_t, arg_t...>;
183 using axisy_t =
typename arg::tpos<tag::axis::y_t, arg_t...>;
184 using width_t =
typename arg::tpos<tag::width_t, arg_t...>;
185 using height_t =
typename arg::tpos<tag::height_t, arg_t...>;
187 static_assert( axisx_t::present,
"x axis must be specified..." );
188 static_assert( axisy_t::present,
"y axis must be specified..." );
190 auto tuple = std::forward_as_tuple(args...);
192 std::array<float,4> margin{5,5,5,5};
193 using margin_t =
typename arg::tpos<tag::margin_t, arg_t...>;
194 if constexpr( margin_t::present )
195 margin = std::get<margin_t::position>( tuple ).value;
197 auto x_axis = std::get<axisx_t::position>( tuple );
198 auto y_axis = std::get<axisy_t::position>( tuple );
203 if constexpr( title_t::present ){
204 x_axis.position = position{ std::get<std::size_t>(x_axis.position->x),
205 std::get<std::size_t>(x_axis.position->y) + std::size_t{14} };
208 float offset_x = std::get<std::size_t>(x_axis.position->x) - .5f * x_axis.grid;
209 float offset_y = std::get<std::size_t>(x_axis.position->y) + .5f * y_axis.grid;
211 float pos_x = x_axis.dx.back() + 1.5f * x_axis.grid + offset_x;
212 y_axis.position = position{
static_cast<std::size_t
>(pos_x),
static_cast<std::size_t
>(offset_y + .5f * y_axis.grid) };
214 std::size_t width, height;
215 if constexpr( width_t::present )
216 width = std::get<width_t::position>( tuple ).value;
218 width =
static_cast<std::size_t
>(y_axis.get_x() + margin[2]);
219 if constexpr( height_t::present )
220 height = std::get<height_t::position>( tuple ).value;
222 height =
static_cast<std::size_t
>(y_axis.get_y() + offset_y + margin[3]);
224 const theme_t& th = impl::resolve_theme(args...);
227 { plot::attribute::element_t bg; bg.color = plot::attribute::color_t{ th.bg };
228 canvas.rect(0, 0,
static_cast<float>(width),
static_cast<float>(height + 10), 0, 0, bg); }
231 x_axis.color = plot::attribute::color_t{ th.fg };
232 y_axis.color = plot::attribute::color_t{ th.fg };
233 canvas << x_axis; canvas << y_axis;
235 if constexpr (title_t::present) {
236 auto title = std::get<title_t::position>( tuple );
237 if( !title.color ) title.color = plot::attribute::color_t{ th.fg };
240 if( !title.position ) title.position = position{ std::size_t{4}, std::size_t{10} };
243 if constexpr (footnote_t::present) canvas << std::get<footnote_t::position>( tuple );
245 plot::attribute::color_t palette{ 0x4060FF };
246 if constexpr (legend_t::present) {
247 canvas << std::get<legend_t::position>( tuple );
248 auto legend = std::get<legend_t::position>( tuple );
249 if( legend.color ) palette = legend.color.value();
251 impl::heatmap(canvas,
252 offset_x, offset_y, x_axis.dx, y_axis.dy,
253 .9f * x_axis.grid, .9f * y_axis.grid, data, palette, th.gradient );
254 return { width, height + 10 };
260 template <
class T,
class... arg_t>
261 void heatmap(std::ostream& os,
const T& data, arg_t... args ) {
266 std::array<float,4> margin{5,5,5,5};
267 using margin_t =
typename arg::tpos<tag::margin_t, arg_t...>;
268 auto mtuple = std::forward_as_tuple(args...);
269 if constexpr( margin_t::present )
270 margin = std::get<margin_t::position>( mtuple ).value;
271 std::pair<std::size_t,std::size_t> sz;
272 { std::ostringstream probe;
273 impl::canvas_t scratch(probe, 1, 1, margin);
274 sz = impl::heatmap_render(scratch, data, args...); }
275 impl::canvas_t canvas(os, sz.first, sz.second, margin);
276 impl::heatmap_render(canvas, data, args...);
280 template <
class T,
class... arg_t>
281 void heatmap(
const std::string& filename,
const T& data, arg_t... args ) {
282 std::ofstream ofs(filename, std::ios::out | std::ios::trunc);
283 heatmap(ofs, data, args...);