10 using namespace display;
12 static const char *
const TAG =
"graph";
13 static const char *
const TAGL =
"graphlegend";
17 this->samples_.resize(length, NAN);
18 this->last_sample_ =
millis();
23 uint32_t dt = tm - last_sample_;
28 while (this->period_ >= this->update_time_) {
29 this->samples_[this->count_] = data;
30 this->period_ -= this->update_time_;
31 this->count_ = (this->count_ + 1) % this->length_;
32 ESP_LOGV(TAG,
"Updating trace with value: %f", data);
34 if (!std::isnan(data)) {
36 this->recent_min_ = data;
37 this->recent_max_ = data;
38 for (
int i = 0; i < this->length_; i++) {
39 if (!std::isnan(this->samples_[i])) {
40 if (this->recent_max_ < this->samples_[i])
41 this->recent_max_ = this->samples_[i];
42 if (this->recent_min_ > this->samples_[i])
43 this->recent_min_ = this->samples_[i];
50 ESP_LOGI(TAG,
"Init trace for sensor %s", this->get_name().c_str());
52 sensor_->add_on_state_callback([
this](
float state) { this->data_.take_sample(state); });
60 buff->
horizontal_line(x_offset, y_offset + this->height_ - 1, this->width_, color);
61 buff->
vertical_line(x_offset, y_offset, this->height_, color);
62 buff->
vertical_line(x_offset + this->width_ - 1, y_offset, this->height_, color);
67 for (
auto *trace : traces_) {
68 float mx = trace->get_tracedata()->get_recent_max();
69 float mn = trace->get_tracedata()->get_recent_min();
70 if (std::isnan(ymax) || (ymax < mx))
72 if (std::isnan(ymin) || (ymin > mn))
76 if (!std::isnan(this->min_value_))
77 ymin = this->min_value_;
78 if (!std::isnan(this->max_value_))
79 ymax = this->max_value_;
81 float yrange = ymax - ymin;
82 if (yrange > this->max_range_) {
86 for (uint32_t i = 0; i < this->width_; i++) {
87 for (
auto *trace : traces_) {
88 float v = trace->get_tracedata()->get_value(i);
90 if ((v - mn) > this->max_range_)
92 if ((mx - v) > this->max_range_)
94 if (std::isnan(mx) || (v > mx))
96 if (std::isnan(mn) || (v < mn))
101 yrange = this->max_range_;
102 if (!std::isnan(mn)) {
104 ymax = ymin + this->max_range_;
106 ESP_LOGV(TAG,
"Graphing at max_range. Using local min %f, max %f", mn, mx);
109 float y_per_div = this->min_range_;
110 if (!std::isnan(this->gridspacing_y_)) {
111 y_per_div = this->gridspacing_y_;
114 if (yrange > 10 * y_per_div) {
115 while (yrange > 10 * y_per_div) {
118 ESP_LOGW(TAG,
"Graphing reducing y-scale to prevent too many gridlines");
124 if (!std::isnan(ymin) && !std::isnan(ymax)) {
125 yn = (int) floorf(ymin / y_per_div);
126 ym = (int) ceilf(ymax / y_per_div);
130 ymin = yn * y_per_div;
131 ymax = ym * y_per_div;
132 yrange = ymax - ymin;
136 if (!std::isnan(this->gridspacing_y_)) {
137 for (
int y = yn;
y <= ym;
y++) {
138 int16_t py = (int16_t) roundf((this->height_ - 1) * (1.0 - (float) (
y - yn) / (ym - yn)));
139 for (uint32_t
x = 0;
x < this->width_;
x += 2) {
144 if (!std::isnan(this->gridspacing_x_) && (this->gridspacing_x_ > 0)) {
145 int n = this->duration_ / this->gridspacing_x_;
151 ESP_LOGW(TAG,
"Graphing reducing x-scale to prevent too many gridlines");
153 for (
int i = 0; i <= n; i++) {
154 for (uint32_t
y = 0;
y < this->height_;
y += 2) {
155 buff->
draw_pixel_at(x_offset + i * (this->width_ - 1) / n, y_offset +
y, color);
161 ESP_LOGV(TAG,
"Updating graph. ymin %f, ymax %f", ymin, ymax);
162 for (
auto *trace : traces_) {
163 Color c = trace->get_line_color();
164 int16_t thick = trace->get_line_thickness();
165 bool continuous = trace->get_continuous();
166 bool has_prev =
false;
169 for (uint32_t i = 0; i < this->width_; i++) {
170 float v = (trace->get_tracedata()->get_value(i) - ymin) / yrange;
171 if (!std::isnan(v) && (thick > 0)) {
172 int16_t
x = this->width_ - 1 - i + x_offset;
174 bool b = (trace->get_line_type() & bit) == bit;
176 int16_t
y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2 + y_offset;
177 auto draw_pixel_at = [&buff, c, y_offset,
this](int16_t
x, int16_t
y) {
178 if (y >= y_offset && y < y_offset + this->height_)
181 if (!continuous || !has_prev || !prev_b || (abs(y - prev_y) <= thick)) {
182 for (int16_t t = 0; t < thick; t++) {
183 draw_pixel_at(x, y + t);
186 int16_t mid_y = (y + prev_y + thick) / 2;
188 for (int16_t t = prev_y + thick; t <= mid_y; t++)
189 draw_pixel_at(x + 1, t);
190 for (int16_t t = mid_y + 1; t < y + thick; t++)
193 for (int16_t t = prev_y - 1; t >= mid_y; t--)
194 draw_pixel_at(x + 1, t);
195 for (int16_t t = mid_y - 1; t >=
y; t--)
215 int txtw = 0, txth = 0;
216 int valw = 0, valh = 0;
218 for (
auto *trace : g->
traces_) {
219 std::string txtstr = trace->get_name();
220 int fw, fos, fbl, fh;
221 this->font_label_->measure(txtstr.c_str(), &fw, &fos, &fbl, &fh);
226 if (trace->get_line_thickness() > lt)
227 lt = trace->get_line_thickness();
228 ESP_LOGI(TAGL,
" %s %d %d", txtstr.c_str(), fw, fh);
234 valstr += trace->sensor_->get_unit_of_measurement();
236 this->font_value_->measure(valstr.c_str(), &fw, &fos, &fbl, &fh);
241 ESP_LOGI(TAGL,
" %s %d %d", valstr.c_str(), fw, fh);
249 uint16_t w = this->width_;
250 uint16_t
h = this->height_;
253 if (!this->font_value_) {
257 this->yl_ = txth + (txth / 4) + lt / 2;
272 this->yv_ = txth + (txth / 4);
274 this->yv_ += txth / 4 + lt;
276 this->xv_ = (txtw + valw) / 2;
281 this->x0_ = txtw / 2;
284 this->xs_ = std::max(txtw, valw);
286 this->x0_ = this->xs_ / 2;
288 this->xs_ = txtw + valw;
291 this->width_ = this->xs_;
293 this->width_ = this->xs_ * n;
297 this->x0_ = this->xs_ / 2;
303 this->ys_ = txth + txth / 2 + valh;
309 this->ys_ = std::max(txth + txth / 4 + lt + txth / 4, valh + valh / 4);
311 this->ys_ = std::max(txth + txth / 4, valh + valh / 4);
313 this->height_ = this->ys_ * n;
316 this->height_ = this->ys_;
318 this->height_ = this->ys_ * n;
337 int w = legend_->width_;
338 int h = legend_->height_;
345 int x = x_offset + legend_->x0_;
347 for (
auto *trace : traces_) {
348 std::string txtstr = trace->get_name();
349 ESP_LOGV(TAG,
" %s", txtstr.c_str());
351 buff->
printf(x, y, legend_->font_label_, trace->get_line_color(), TextAlign::TOP_CENTER,
"%s", txtstr.c_str());
353 if (legend_->lines_) {
354 uint16_t thick = trace->get_line_thickness();
355 for (
int i = 0; i < legend_->x0_ * 4 / 3; i++) {
357 if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) {
358 buff->
vertical_line(x - legend_->x0_ * 2 / 3 + i, y + legend_->yl_ - thick / 2, thick,
359 trace->get_line_color());
365 int xv = x + legend_->xv_;
366 int yv = y + legend_->yv_;
369 if (legend_->units_) {
370 valstr += trace->sensor_->get_unit_of_measurement();
372 buff->
printf(xv, yv, legend_->font_value_, trace->get_line_color(), TextAlign::TOP_CENTER,
"%s", valstr.c_str());
373 ESP_LOGV(TAG,
" value: %s", valstr.c_str());
381 for (
auto *trace : traces_) {
387 for (
auto *trace : traces_) {
388 ESP_LOGCONFIG(TAG,
"Graph for sensor %s", trace->get_name().c_str());
void horizontal_line(int x, int y, int width, Color color=COLOR_ON)
Draw a horizontal line from the point [x,y] to [x+width,y] with the given color.
std::string value_accuracy_to_string(float value, int8_t accuracy_decimals)
Create a string from a value and an accuracy in decimals.
void take_sample(float data)
uint32_t IRAM_ATTR HOT millis()
std::vector< GraphTrace * > traces_
void dump_config() override
void draw_legend(display::Display *buff, uint16_t x_offset, uint16_t y_offset, Color color)
void init(Graph *g)
Determine the best coordinates of drawing text + lines.
void vertical_line(int x, int y, int height, Color color=COLOR_ON)
Draw a vertical line from the point [x,y] to [x,y+width] with the given color.
void draw_pixel_at(int x, int y)
Set a single pixel at the specified coordinates to default color.
void printf(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format,...) __attribute__((format(printf
Evaluate the printf-format format and print the result with the anchor point at [x,y] with font.
Implementation of SPI Controller mode.
void draw(display::Display *buff, uint16_t x_offset, uint16_t y_offset, Color color)