13 using namespace display;
15 static const char *
const TAG =
"graph";
16 static const char *
const TAGL =
"graphlegend";
20 this->samples_.resize(length, NAN);
21 this->last_sample_ =
millis();
26 uint32_t dt = tm - last_sample_;
31 while (this->period_ >= this->update_time_) {
32 this->samples_[this->count_] = data;
33 this->period_ -= this->update_time_;
34 this->count_ = (this->count_ + 1) % this->length_;
35 ESP_LOGV(TAG,
"Updating trace with value: %f", data);
37 if (!std::isnan(data)) {
39 this->recent_min_ = data;
40 this->recent_max_ = data;
41 for (
int i = 0; i < this->length_; i++) {
42 if (!std::isnan(this->samples_[i])) {
43 if (this->recent_max_ < this->samples_[i])
44 this->recent_max_ = this->samples_[i];
45 if (this->recent_min_ > this->samples_[i])
46 this->recent_min_ = this->samples_[i];
53 ESP_LOGI(TAG,
"Init trace for sensor %s", this->get_name().c_str());
55 sensor_->add_on_state_callback([
this](
float state) { this->data_.take_sample(state); });
63 buff->
horizontal_line(x_offset, y_offset + this->height_ - 1, this->width_, color);
64 buff->
vertical_line(x_offset, y_offset, this->height_, color);
65 buff->
vertical_line(x_offset + this->width_ - 1, y_offset, this->height_, color);
70 for (
auto *trace : traces_) {
71 float mx = trace->get_tracedata()->get_recent_max();
72 float mn = trace->get_tracedata()->get_recent_min();
73 if (std::isnan(ymax) || (ymax < mx))
75 if (std::isnan(ymin) || (ymin > mn))
79 if (!std::isnan(this->min_value_))
80 ymin = this->min_value_;
81 if (!std::isnan(this->max_value_))
82 ymax = this->max_value_;
84 float yrange = ymax - ymin;
85 if (yrange > this->max_range_) {
89 for (uint32_t i = 0; i < this->width_; i++) {
90 for (
auto *trace : traces_) {
91 float v = trace->get_tracedata()->get_value(i);
93 if ((v - mn) > this->max_range_)
95 if ((mx - v) > this->max_range_)
97 if (std::isnan(mx) || (v > mx))
99 if (std::isnan(mn) || (v < mn))
104 yrange = this->max_range_;
105 if (!std::isnan(mn)) {
107 ymax = ymin + this->max_range_;
109 ESP_LOGV(TAG,
"Graphing at max_range. Using local min %f, max %f", mn, mx);
112 float y_per_div = this->min_range_;
113 if (!std::isnan(this->gridspacing_y_)) {
114 y_per_div = this->gridspacing_y_;
117 if (yrange > 10 * y_per_div) {
118 while (yrange > 10 * y_per_div) {
121 ESP_LOGW(TAG,
"Graphing reducing y-scale to prevent too many gridlines");
127 if (!std::isnan(ymin) && !std::isnan(ymax)) {
128 yn = (int) floorf(ymin / y_per_div);
129 ym = (int) ceilf(ymax / y_per_div);
133 ymin = yn * y_per_div;
134 ymax = ym * y_per_div;
135 yrange = ymax - ymin;
139 if (!std::isnan(this->gridspacing_y_)) {
140 for (
int y = yn;
y <= ym;
y++) {
141 int16_t py = (int16_t) roundf((this->height_ - 1) * (1.0 - (float) (
y - yn) / (ym - yn)));
142 for (uint32_t
x = 0;
x < this->width_;
x += 2) {
147 if (!std::isnan(this->gridspacing_x_) && (this->gridspacing_x_ > 0)) {
148 int n = this->duration_ / this->gridspacing_x_;
154 ESP_LOGW(TAG,
"Graphing reducing x-scale to prevent too many gridlines");
156 for (
int i = 0; i <= n; i++) {
157 for (uint32_t
y = 0;
y < this->height_;
y += 2) {
158 buff->
draw_pixel_at(x_offset + i * (this->width_ - 1) / n, y_offset +
y, color);
164 ESP_LOGV(TAG,
"Updating graph. ymin %f, ymax %f", ymin, ymax);
165 for (
auto *trace : traces_) {
166 Color c = trace->get_line_color();
167 uint16_t thick = trace->get_line_thickness();
168 for (uint32_t i = 0; i < this->width_; i++) {
169 float v = (trace->get_tracedata()->get_value(i) - ymin) / yrange;
170 if (!std::isnan(v) && (thick > 0)) {
171 int16_t
x = this->width_ - 1 - i;
173 if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) {
174 int16_t
y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2;
175 for (uint16_t t = 0; t < thick; t++) {
189 int txtw = 0, txth = 0;
190 int valw = 0, valh = 0;
192 for (
auto *trace : g->
traces_) {
193 std::string txtstr = trace->get_name();
194 int fw, fos, fbl, fh;
195 this->font_label_->measure(txtstr.c_str(), &fw, &fos, &fbl, &fh);
200 if (trace->get_line_thickness() > lt)
201 lt = trace->get_line_thickness();
202 ESP_LOGI(TAGL,
" %s %d %d", txtstr.c_str(), fw, fh);
205 std::stringstream ss;
206 ss << std::fixed << std::setprecision(trace->sensor_->get_accuracy_decimals()) << trace->sensor_->get_state();
207 std::string valstr = ss.str();
209 valstr += trace->sensor_->get_unit_of_measurement();
211 this->font_value_->measure(valstr.c_str(), &fw, &fos, &fbl, &fh);
216 ESP_LOGI(TAGL,
" %s %d %d", valstr.c_str(), fw, fh);
224 uint16_t w = this->width_;
225 uint16_t
h = this->height_;
228 if (!this->font_value_) {
232 this->yl_ = txth + (txth / 4) + lt / 2;
247 this->yv_ = txth + (txth / 4);
249 this->yv_ += txth / 4 + lt;
251 this->xv_ = (txtw + valw) / 2;
256 this->x0_ = txtw / 2;
259 this->xs_ = std::max(txtw, valw);
261 this->x0_ = this->xs_ / 2;
263 this->xs_ = txtw + valw;
266 this->width_ = this->xs_;
268 this->width_ = this->xs_ * n;
272 this->x0_ = this->xs_ / 2;
278 this->ys_ = txth + txth / 2 + valh;
284 this->ys_ = std::max(txth + txth / 4 + lt + txth / 4, valh + valh / 4);
286 this->ys_ = std::max(txth + txth / 4, valh + valh / 4);
288 this->height_ = this->ys_ * n;
291 this->height_ = this->ys_;
293 this->height_ = this->ys_ * n;
312 int w = legend_->width_;
313 int h = legend_->height_;
320 int x = x_offset + legend_->x0_;
322 for (
auto *trace : traces_) {
323 std::string txtstr = trace->get_name();
324 ESP_LOGV(TAG,
" %s", txtstr.c_str());
326 buff->
printf(x, y, legend_->font_label_, trace->get_line_color(), TextAlign::TOP_CENTER,
"%s", txtstr.c_str());
328 if (legend_->lines_) {
329 uint16_t thick = trace->get_line_thickness();
330 for (
int i = 0; i < legend_->x0_ * 4 / 3; i++) {
332 if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) {
333 buff->
vertical_line(x - legend_->x0_ * 2 / 3 + i, y + legend_->yl_ - thick / 2, thick,
334 trace->get_line_color());
340 int xv = x + legend_->xv_;
341 int yv = y + legend_->yv_;
342 std::stringstream ss;
343 ss << std::fixed << std::setprecision(trace->sensor_->get_accuracy_decimals()) << trace->sensor_->get_state();
344 std::string valstr = ss.str();
345 if (legend_->units_) {
346 valstr += trace->sensor_->get_unit_of_measurement();
348 buff->
printf(xv, yv, legend_->font_value_, trace->get_line_color(), TextAlign::TOP_CENTER,
"%s", valstr.c_str());
349 ESP_LOGV(TAG,
" value: %s", valstr.c_str());
357 for (
auto *trace : traces_) {
363 for (
auto *trace : traces_) {
364 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.
void take_sample(float data)
void printf(int x, int y, BaseFont *font, Color color, 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.
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.
Implementation of SPI Controller mode.
void draw(display::Display *buff, uint16_t x_offset, uint16_t y_offset, Color color)