From 559042da785233fa7af65349597f793fdd73dfb1 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Mon, 5 Jan 2026 23:19:12 +0100 Subject: [PATCH] Code rework for OBPcharts, part 1 --- lib/obp60task/OBP60Formatter.cpp | 13 +- lib/obp60task/OBPcharts.cpp | 673 ++++++++++++++++++------------- lib/obp60task/OBPcharts.h | 52 ++- lib/obp60task/PageOneValue.cpp | 28 +- lib/obp60task/PageWindPlot.cpp | 23 +- lib/obp60task/Pagedata.h | 3 + 6 files changed, 458 insertions(+), 334 deletions(-) diff --git a/lib/obp60task/OBP60Formatter.cpp b/lib/obp60task/OBP60Formatter.cpp index 830a846..06d3dc5 100644 --- a/lib/obp60task/OBP60Formatter.cpp +++ b/lib/obp60task/OBP60Formatter.cpp @@ -878,19 +878,26 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ } // Helper method for conversion of boat data values from SI to user defined format -double convertValue(const double &value, const String &format, CommonData &commondata) +double convertValue(const double &value, const String &name, const String &format, CommonData &commondata) { std::unique_ptr tmpBValue; // Temp variable to get converted data value from double result; // data value converted to user defined target data format - // prepare dummy BoatValue structure for use in - tmpBValue = std::unique_ptr(new GwApi::BoatValue("dummy")); // we don't need boat value name for pure value conversion + // prepare temporary BoatValue structure for use in + tmpBValue = std::unique_ptr(new GwApi::BoatValue(name)); // we don't need boat value name for pure value conversion tmpBValue->setFormat(format); tmpBValue->valid = true; tmpBValue->value = value; result = formatValue(tmpBValue.get(), commondata).cvalue; // get value (converted) + return result; +} +double convertValue(const double &value, const String &format, CommonData &commondata) +{ + double result; // data value converted to user defined target data format + + result = convertValue(value, "dummy", format, commondata); return result; } diff --git a/lib/obp60task/OBPcharts.cpp b/lib/obp60task/OBPcharts.cpp index 3143273..9830653 100644 --- a/lib/obp60task/OBPcharts.cpp +++ b/lib/obp60task/OBPcharts.cpp @@ -1,6 +1,6 @@ // Function lib for display of boat data in various chart formats #include "OBPcharts.h" -#include "OBP60Extensions.h" +// #include "OBP60Extensions.h" #include "OBPDataOperations.h" #include "OBPRingBuffer.h" @@ -25,18 +25,6 @@ Chart::Chart(RingBuffer& dataBuf, char chrtDir, int8_t chrtSz, double dflt fgColor = commonData->fgcolor; bgColor = commonData->bgcolor; - // we need "0" value for any user defined temperature format - tempFormat = commonData->config->getString(commonData->config->tempFormat); // [K|°C|°F] - if (tempFormat == "K") { - zeroValue = 0.0; - } else if (tempFormat == "C") { - zeroValue = 273.15; - } else if (tempFormat == "F") { - zeroValue = 255.37; - } - // LOG_DEBUG(GwLog::DEBUG, "Chart-init: fgColor: %d, bgColor: %d, tempFormat: %s, zeroValue: %.1f, &commonData: %p", fgColor, bgColor, tempFormat, zeroValue, commonData); - - // LOG_DEBUG(GwLog::DEBUG, "Chart Init: Chart::dataBuf: %p, passed dataBuf: %p", (void*)&this->dataBuf, (void*)&dataBuf); dWidth = getdisplay().width(); dHeight = getdisplay().height(); @@ -46,18 +34,18 @@ Chart::Chart(RingBuffer& dataBuf, char chrtDir, int8_t chrtSz, double dflt switch (chrtSz) { case 0: valAxis = dHeight - top - bottom; - cStart = { 0, top - 1 }; + cRoot = { 0, top - 1 }; break; case 1: valAxis = (dHeight - top - bottom) / 2 - hGap; - cStart = { 0, top - 1 }; + cRoot = { 0, top - 1 }; break; case 2: valAxis = (dHeight - top - bottom) / 2 - hGap; - cStart = { 0, top + (valAxis + hGap) + hGap - 1 }; + cRoot = { 0, top + (valAxis + hGap) + hGap - 1 }; break; default: - LOG_DEBUG(GwLog::ERROR, "displayChart: wrong init parameter"); + LOG_DEBUG(GwLog::ERROR, "obp60:Chart %s: wrong init parameter", dataBuf.getName()); return; } @@ -67,22 +55,22 @@ Chart::Chart(RingBuffer& dataBuf, char chrtDir, int8_t chrtSz, double dflt switch (chrtSz) { case 0: valAxis = dWidth - 1; - cStart = { 0, top - 1 }; + cRoot = { 0, top - 1 }; break; case 1: valAxis = dWidth / 2 - vGap; - cStart = { 0, top - 1 }; + cRoot = { 0, top - 1 }; break; case 2: valAxis = dWidth / 2 - vGap; - cStart = { dWidth / 2 + vGap - 1, top - 1 }; + cRoot = { dWidth / 2 + vGap - 1, top - 1 }; break; default: - LOG_DEBUG(GwLog::ERROR, "displayChart: wrong init parameter"); + LOG_DEBUG(GwLog::ERROR, "obp60:Chart %s: wrong init parameter", dataBuf.getName()); return; } } else { - LOG_DEBUG(GwLog::ERROR, "displayChart: wrong init parameter"); + LOG_DEBUG(GwLog::ERROR, "obp60:Chart %s: wrong init parameter", dataBuf.getName()); return; } @@ -91,34 +79,55 @@ Chart::Chart(RingBuffer& dataBuf, char chrtDir, int8_t chrtSz, double dflt dbMAX_VAL = dataBuf.getMaxVal(); bufSize = dataBuf.getCapacity(); + // Initialize chart data format; shorter version of standard format indicator if (dbFormat == "formatCourse" || dbFormat == "formatWind" || dbFormat == "formatRot") { - - if (dbFormat == "formatRot") { - chrtDataFmt = 'R'; // Chart is showing data of rotational format - } else { - chrtDataFmt = 'W'; // Chart is showing data of course / wind format - } - rngStep = M_TWOPI / 360.0 * 10.0; // +/-10 degrees on each end of chrtMid; we are calculating with SI values - + chrtDataFmt = 'W'; // Chart is showing data of course / wind format + } else if (dbFormat == "formatRot") { + chrtDataFmt = 'R'; // Chart is showing data of rotational format + } else if (dbFormat == "formatKnots") { + chrtDataFmt = 'S'; // Chart is showing data of speed or windspeed format + } else if (dbFormat == "formatDepth") { + chrtDataFmt = 'D'; // Chart ist showing data of format + } else if (dbFormat == "kelvinToC") { + chrtDataFmt = 'T'; // Chart ist showing data of format } else { - if (dbFormat == "formatDepth") { - chrtDataFmt = 'D'; // Chart ist showing data of format - } else if (dbFormat == "kelvinToC") { - chrtDataFmt = 'T'; // Chart ist showing data of format - } else { - chrtDataFmt = 'S'; // Chart is showing any other data format - } - rngStep = 10.0; // +/- 10 for all other values (eg. m/s, m, K, mBar) + chrtDataFmt = 'O'; // Chart is showing any other data format } - chrtMin = dbMIN_VAL; - chrtMax = dbMAX_VAL; - chrtMid = dbMAX_VAL; + // "0" value is the same for any data format but for user defined temperature format + zeroValue = 0.0; + if (chrtDataFmt == 'T') { + tempFormat = commonData->config->getString(commonData->config->tempFormat); // [K|°C|°F] + if (tempFormat == "K") { + zeroValue = 0.0; + } else if (tempFormat == "C") { + zeroValue = 273.15; + } else if (tempFormat == "F") { + zeroValue = 255.37; + } + } + + // Read default range and range step for this chart type + if (dfltChrtDta.count(dbFormat)) { + dfltRng = dfltChrtDta[dbFormat].range; + rngStep = dfltChrtDta[dbFormat].step; + } else { + dfltRng = 15.0; + rngStep = 5.0; + } + + //chrtMin = dbMIN_VAL; + //chrtMax = dbMAX_VAL; + //chrtMid = dbMAX_VAL; + // Initialize chart range values + chrtMin = zeroValue; + chrtMax = chrtMin + dfltRng; + chrtMid = (chrtMin + chrtMax) / 2; chrtRng = dfltRng; recalcRngCntr = true; // initialize and chart borders on first screen call - LOG_DEBUG(GwLog::DEBUG, "Chart Init: dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cStart {x,y}: %d, %d, dbname: %s, rngStep: %.4f, chrtDataFmt: %c", - dWidth, dHeight, timAxis, valAxis, cStart.x, cStart.y, dbName, rngStep, chrtDataFmt); + LOG_DEBUG(GwLog::DEBUG, "Chart Init: dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cRoot {x,y}: %d, %d, dbname: %s, rngStep: %.4f, chrtDataFmt: %c", + dWidth, dHeight, timAxis, valAxis, cRoot.x, cRoot.y, dbName, rngStep, chrtDataFmt); }; template @@ -129,67 +138,34 @@ Chart::~Chart() // Perform all actions to draw chart // Parameters: chart time interval, current boat data value to be printed, current boat data shall be shown yes/no template -void Chart::showChrt(GwApi::BoatValue currValue, int8_t chrtIntv, bool showCurrValue) +void Chart::showChrt(GwApi::BoatValue currValue, int8_t& chrtIntv, const bool showCurrValue) { drawChrt(chrtIntv, currValue); drawChrtTimeAxis(chrtIntv); drawChrtValAxis(); - if (bufDataValid) { - if (showCurrValue) { - // uses BoatValue temp variable to format latest buffer value - // doesn't work unfortunately when 'simulation data' is active, because OBP60Formatter generates own simulation values in that case - currValue.value = dataBuf.getLast(); - currValue.valid = currValue.value != dbMAX_VAL; - Chart::prntCurrValue(currValue); - } + if (!bufDataValid) { // No valid data available + prntNoValidData(); + return; + } - } else { // No valid data available -> print message - getdisplay().setFont(&Ubuntu_Bold10pt8b); - - int pX, pY; - if (chrtDir == 'H') { - pX = cStart.x + (timAxis / 2); - pY = cStart.y + (valAxis / 2) - 10; - } else { - pX = cStart.x + (valAxis / 2); - pY = cStart.y + (timAxis / 2) - 10; - } - - getdisplay().fillRect(pX - 37, pY - 10, 78, 24, bgColor); // Clear area for message - drawTextCenter(pX, pY, "No data"); - LOG_DEBUG(GwLog::LOG, "Page chart: No valid data available"); + if (showCurrValue) { // shows latest value from history buffer; usually this should be the most current one + currValue.value = dataBuf.getLast(); + currValue.valid = currValue.value != dbMAX_VAL; + Chart::prntCurrValue(currValue); } } // draw chart template -void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) +void Chart::drawChrt(int8_t& chrtIntv, GwApi::BoatValue& currValue) { double chrtVal; // Current data value double chrtScl; // Scale for data values in pixels per value int x, y; // x and y coordinates for drawing - // Identify buffer size and buffer start position for chart - count = dataBuf.getCurrentSize(); - currIdx = dataBuf.getLastIdx(); - numAddedBufVals = (currIdx - lastAddedIdx + bufSize) % bufSize; // Number of values added to buffer since last display - - if (chrtIntv != oldChrtIntv || count == 1) { - // new data interval selected by user; this is only x * 230 values instead of 240 seconds (4 minutes) per interval step - numBufVals = min(count, (timAxis - 60) * chrtIntv); // keep free or release 60 values on chart for plotting of new values - bufStart = max(0, count - numBufVals); - lastAddedIdx = currIdx; - oldChrtIntv = chrtIntv; - - } else { - numBufVals = numBufVals + numAddedBufVals; - lastAddedIdx = currIdx; - if (count == bufSize) { - bufStart = max(0, bufStart - numAddedBufVals); - } - } + getBufStartNSize(chrtIntv); // LOG_DEBUG(GwLog::DEBUG, "PageOneValue:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", chrtMin, chrtMid, chrtMax, chrtRng); calcChrtBorders(chrtMin, chrtMid, chrtMax, chrtRng); @@ -204,7 +180,7 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) } else if (!currValue.valid && !useSimuData) { // currently no valid boat data available and no simulation mode numNoData++; bufDataValid = true; - if (numNoData > 3) { // If more than 4 invalid values in a row, send message + if (numNoData > THRESHOLD_NO_DATA) { // If more than 4 invalid values in a row, send message bufDataValid = false; } } else { @@ -222,23 +198,24 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) } else { if (chrtDir == 'H') { // horizontal chart - x = cStart.x + i; // Position in chart area + x = cRoot.x + i; // Position in chart area - if (chrtDataFmt == 'S' or chrtDataFmt == 'T') { // speed data format -> print low values at bottom - y = cStart.y + valAxis - static_cast(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round - } else if (chrtDataFmt == 'D') { - y = cStart.y + static_cast(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round - } else { // degree type value - y = cStart.y + static_cast((WindUtils::to2PI(chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round + if (chrtDataFmt == 'S' or chrtDataFmt == 'T') { // speed or temperature data format -> print low values at bottom + y = cRoot.y + valAxis - static_cast(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round + } else if (chrtDataFmt == 'W' || chrtDataFmt == 'R') { // degree type value + y = cRoot.y + static_cast((WindUtils::to2PI(chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round + } else { // any other data format + y = cRoot.y + static_cast(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round } } else { // vertical chart - y = cStart.y + timAxis - i; // Position in chart area + y = cRoot.y + timAxis - i; // Position in chart area - if (chrtDataFmt == 'S' || chrtDataFmt == 'D' || chrtDataFmt == 'T') { - x = cStart.x + static_cast(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round - } else { // degree type value - x = cStart.x + static_cast((WindUtils::to2PI(chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round + // if (chrtDataFmt == 'S' || chrtDataFmt == 'D' || chrtDataFmt == 'T') { + if (chrtDataFmt == 'W' || chrtDataFmt == 'R') { // degree type value + x = cRoot.x + static_cast((WindUtils::to2PI(chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round + } else { + x = cRoot.x + static_cast(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round } } @@ -261,45 +238,44 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: crossedBorders: %d, chrtVal: %.2f, chrtPrevVal: %.2f", crossedBorders, chrtVal, chrtPrevVal); bool wrappingFromHighToLow = normCurr < normPrev; // Determine which edge we're crossing if (chrtDir == 'H') { - int ySplit = wrappingFromHighToLow ? (cStart.y + valAxis) : cStart.y; - getdisplay().drawLine(prevX, prevY, x, ySplit, fgColor); - if (x != prevX) { // line with some horizontal trend - getdisplay().drawLine(prevX, prevY - 1, x, ySplit - 1, fgColor); - } else { - getdisplay().drawLine(prevX, prevY - 1, x - 1, ySplit, fgColor); - } - prevY = wrappingFromHighToLow ? cStart.y : (cStart.y + valAxis); + int ySplit = wrappingFromHighToLow ? (cRoot.y + valAxis) : cRoot.y; + drawBoldLine(prevX, prevY, x, ySplit); + prevY = wrappingFromHighToLow ? cRoot.y : (cRoot.y + valAxis); } else { // vertical chart - int xSplit = wrappingFromHighToLow ? (cStart.x + valAxis) : cStart.x; - getdisplay().drawLine(prevX, prevY, xSplit, y, fgColor); - getdisplay().drawLine(prevX, prevY - 1, ((xSplit != prevX) ? xSplit : xSplit - 1), ((xSplit != prevX) ? y - 1 : y), fgColor); - prevX = wrappingFromHighToLow ? cStart.x : (cStart.x + valAxis); + int xSplit = wrappingFromHighToLow ? (cRoot.x + valAxis) : cRoot.x; + drawBoldLine(prevX, prevY, xSplit, y); + prevX = wrappingFromHighToLow ? cRoot.x : (cRoot.x + valAxis); } } } - // Draw line with 2 pixels width + make sure vertical lines are drawn correctly - if (chrtDir == 'H' || x == prevX) { // horizontal chart & vertical line - if (chrtDataFmt == 'D') { - getdisplay().drawLine(x, y, x, cStart.y + valAxis, fgColor); - getdisplay().drawLine(x - 1, y, x - 1, cStart.y + valAxis, fgColor); + if (chrtDataFmt == 'D') { + if (chrtDir == 'H') { // horizontal chart + drawBoldLine(x, y, x, cRoot.y + valAxis); + } else { // vertical chart + drawBoldLine(x, y, cRoot.x + valAxis, y); } - getdisplay().drawLine(prevX, prevY, x, y, fgColor); - getdisplay().drawLine(prevX - 1, prevY, x - 1, y, fgColor); - } else if (chrtDir == 'V' || x != prevX) { // vertical chart & line with some horizontal trend -> normal state - if (chrtDataFmt == 'D') { - getdisplay().drawLine(x, y, cStart.x + valAxis, y, fgColor); - getdisplay().drawLine(x, y - 1, cStart.x + valAxis, y - 1, fgColor); - } - getdisplay().drawLine(prevX, prevY, x, y, fgColor); - getdisplay().drawLine(prevX, prevY - 1, x, y - 1, fgColor); + } else { + drawBoldLine(prevX, prevY, x, y); } + + /* if (chrtDir == 'H' || x == prevX) { // horizontal chart & vertical line + if (chrtDataFmt == 'D') { + drawBoldLine(x, y, x, cRoot.y + valAxis); + } + drawBoldLine(prevX, prevY, x, y); + } else if (chrtDir == 'V' || x != prevX) { // vertical chart & line with some horizontal trend -> normal state + if (chrtDataFmt == 'D') { + drawBoldLine(x, y, cRoot.x + valAxis, y); + } + drawBoldLine(prevX, prevY, x, y); + } */ chrtPrevVal = chrtVal; prevX = x; prevY = y; } - // Reaching chart area bottom end + // Reaching chart area top end if (i >= timAxis - 1) { oldChrtIntv = 0; // force reset of buffer start and number of values to show in next display loop @@ -313,6 +289,30 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) } } +// Identify buffer size and buffer start position for chart +template +void Chart::getBufStartNSize(int8_t& chrtIntv) +{ + count = dataBuf.getCurrentSize(); + currIdx = dataBuf.getLastIdx(); + numAddedBufVals = (currIdx - lastAddedIdx + bufSize) % bufSize; // Number of values added to buffer since last display + + if (chrtIntv != oldChrtIntv || count == 1) { + // new data interval selected by user; this is only x * 230 values instead of 240 seconds (4 minutes) per interval step + numBufVals = min(count, (timAxis - MIN_FREE_VALUES) * chrtIntv); // keep free or release MIN_FREE_VALUES on chart for plotting of new values + bufStart = max(0, count - numBufVals); + lastAddedIdx = currIdx; + oldChrtIntv = chrtIntv; + + } else { + numBufVals = numBufVals + numAddedBufVals; + lastAddedIdx = currIdx; + if (count == bufSize) { + bufStart = max(0, bufStart - numAddedBufVals); + } + } +} + // check and adjust chart range and set range borders and range middle template void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, double& rng) @@ -371,94 +371,90 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, d rngMax = WindUtils::to2PI(rngMax); rng = halfRng * 2.0; - LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: rngMid: %.1f°, rngMin: %.1f°, rngMax: %.1f°, diffRng: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMid * RAD_TO_DEG, rngMin * RAD_TO_DEG, rngMax * RAD_TO_DEG, - diffRng * RAD_TO_DEG, rng * RAD_TO_DEG, rngStep * RAD_TO_DEG); + LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: rngMin: %.1f°, rngMid: %.1f°, rngMax: %.1f°, diffRng: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMin * RAD_TO_DEG, rngMid * RAD_TO_DEG, rngMax * RAD_TO_DEG, + diffRng * RAD_TO_DEG, rng * RAD_TO_DEG, rngStep * RAD_TO_DEG); } else { // chart data is of any other type double oldRngMin = rngMin; double oldRngMax = rngMax; - // calculate low end range value double currMinVal = dataBuf.getMin(numBufVals); - LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: currMinVal: %.1f, currMaxVal: %.1f, rngMin: %.1f, rngMid: %.1f, rngMax: %.1f, rng: %.1f, rngStep: %.1f, zeroValue: %.1f, oldRngMin: %.1f, oldRngMax: %.1f, dfltRng: %.1f, dbMIN_VAL: %.1f", - currMinVal, dataBuf.getMax(numBufVals), rngMin, rngMid, rngMax, rng, rngStep, zeroValue, oldRngMin, oldRngMax, dfltRng, dbMIN_VAL); - - if (currMinVal != dbMAX_VAL) { // current min value is valid - - if (currMinVal < oldRngMin || (currMinVal > (oldRngMin + rngStep))) { // recalculate rngMin if required or increase if lowest value is higher than old rngMin - // rngMin = std::floor(currMinVal / rngStep) * rngStep; // align low range to lowest buffer value and nearest range interval - rngMin = currMinVal; - LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders2: currMinVal: %.1f, rngMin: %.1f, oldRngMin: %.1f, zeroValue: %.1f", currMinVal, rngMin, oldRngMin, zeroValue); - } - if (rngMin > zeroValue && dbMIN_VAL <= zeroValue) { // Chart range starts at least at '0' if minimum data value allows it - rngMin = zeroValue; - LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders3: currMinVal: %.1f, rngMin: %.1f, oldRngMin: %.1f, zeroValue: %.1f", currMinVal, rngMin, oldRngMin, zeroValue); - } - } // otherwise keep rngMin unchanged - double currMaxVal = dataBuf.getMax(numBufVals); - if (currMaxVal != dbMAX_VAL) { // current max value is valid - if ((currMaxVal > oldRngMax) || (currMaxVal < (oldRngMax - rngStep))) { // increase rngMax if required or decrease if lowest value is lower than old rngMax - // rngMax = std::ceil(currMaxVal / rngStep) * rngStep; - rngMax = currMaxVal; - rngMax = std::max(rngMax, rngMin + dfltRng); // keep at least default chart range - } - } // otherwise keep rngMax unchanged + + if (currMinVal == dbMAX_VAL || currMaxVal == dbMAX_VAL) { + return; // no valid data + } + + // check if current chart border have to be adjusted + if (currMinVal < oldRngMin || (currMinVal > (oldRngMin + rngStep))) { // decrease rngMin if required or increase if lowest value is higher than old rngMin + rngMin = std::floor(currMinVal / rngStep) * rngStep; // align low range to lowest buffer value and nearest range interval + } + if ((currMaxVal > oldRngMax) || (currMaxVal < (oldRngMax - rngStep))) { // increase rngMax if required or decrease if lowest value is lower than old rngMax + rngMax = std::ceil(currMaxVal / rngStep) * rngStep; + } + + // Chart range starts at least at '0' if minimum data value allows it + if (rngMin > zeroValue && dbMIN_VAL <= zeroValue) { + rngMin = zeroValue; + } + + // ensure minimum chart range in user format + if ((rngMax - rngMin) < dfltRng) { + rngMax = rngMin + dfltRng; + } rngMid = (rngMin + rngMax) / 2.0; rng = rngMax - rngMin; - // LOG_DEBUG(GwLog::DEBUG, "calcChrtRange-end: currMinVal: %.1f, currMaxVal: %.1f, rngMin: %.1f, rngMid: %.1f, rngMax: %.1f, rng: %.1f, rngStep: %.1f, oldRngMin: %.1f, oldRngMax: %.1f, dfltRng: %.1f, numBufVals: %d", - // currMinVal, currMaxVal, rngMin, rngMid, rngMax, rng, rngStep, oldRngMin, oldRngMax, dfltRng, numBufVals); + + LOG_DEBUG(GwLog::DEBUG, "calcChrtRange-end: currMinVal: %.1f, currMaxVal: %.1f, rngMin: %.1f, rngMid: %.1f, rngMax: %.1f, rng: %.1f, rngStep: %.1f, zeroValue: %.1f, dbMIN_VAL: %.1f", + currMinVal, currMaxVal, rngMin, rngMid, rngMax, rng, rngStep, zeroValue, dbMIN_VAL); } } // chart time axis label + lines template -void Chart::drawChrtTimeAxis(int8_t chrtIntv) +void Chart::drawChrtTimeAxis(int8_t& chrtIntv) { - float slots, intv, i; + float axSlots, intv, i; char sTime[6]; int timeRng = chrtIntv * 4; // chart time interval: [1] 4 min., [2] 8 min., [3] 12 min., [4] 16 min., [8] 32 min. getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setTextColor(fgColor); - if (chrtDir == 'H') { // horizontal chart - getdisplay().fillRect(0, cStart.y, dWidth, 2, fgColor); + axSlots = 5; // number of axis labels + intv = timAxis / (axSlots - 1); // minutes per chart axis interval (interval is 1 less than axSlots) + i = timeRng; // Chart axis label start at -32, -16, -12, ... minutes - slots = 5; // number of axis labels - intv = timAxis / (slots - 1); // minutes per chart axis interval (interval is 1 less than slots) - i = timeRng; // Chart axis label start at -32, -16, -12, ... minutes + if (chrtDir == 'H') { // horizontal chart + getdisplay().fillRect(0, cRoot.y, dWidth, 2, fgColor); for (float j = 0; j < timAxis - 1; j += intv) { // fill time axis with values but keep area free on right hand side for value label // draw text with appropriate offset int tOffset = j == 0 ? 13 : -4; snprintf(sTime, sizeof(sTime), "-%.0f", i); - drawTextCenter(cStart.x + j + tOffset, cStart.y - 8, sTime); - getdisplay().drawLine(cStart.x + j, cStart.y, cStart.x + j, cStart.y + 5, fgColor); // draw short vertical time mark + drawTextCenter(cRoot.x + j + tOffset, cRoot.y - 8, sTime); + getdisplay().drawLine(cRoot.x + j, cRoot.y, cRoot.x + j, cRoot.y + 5, fgColor); // draw short vertical time mark i -= chrtIntv; } } else { // vertical chart - slots = 5; // number of axis labels - intv = timAxis / (slots - 1); // minutes per chart axis interval (interval is 1 less than slots) - i = timeRng; // Chart axis label start at -32, -16, -12, ... minutes for (float j = intv; j < timAxis - 1; j += intv) { // don't print time label at upper and lower end of time axis i -= chrtIntv; // we start not at top chart position snprintf(sTime, sizeof(sTime), "-%.0f", i); - getdisplay().drawLine(cStart.x, cStart.y + j, cStart.x + valAxis, cStart.y + j, fgColor); // Grid line + getdisplay().drawLine(cRoot.x, cRoot.y + j, cRoot.x + valAxis, cRoot.y + j, fgColor); // Grid line if (chrtSz == 0) { // full size chart - getdisplay().fillRect(0, cStart.y + j - 9, 32, 15, bgColor); // clear small area to remove potential chart lines - getdisplay().setCursor((4 - strlen(sTime)) * 7, cStart.y + j + 3); // time value; print left screen; value right-formated + getdisplay().fillRect(0, cRoot.y + j - 9, 32, 15, bgColor); // clear small area to remove potential chart lines + getdisplay().setCursor((4 - strlen(sTime)) * 7, cRoot.y + j + 3); // time value; print left screen; value right-formated getdisplay().printf("%s", sTime); // Range value } else if (chrtSz == 2) { // half size chart; right side - drawTextCenter(dWidth / 2, cStart.y + j, sTime); // time value; print mid screen + drawTextCenter(dWidth / 2, cRoot.y + j, sTime); // time value; print mid screen } } } @@ -468,142 +464,75 @@ void Chart::drawChrtTimeAxis(int8_t chrtIntv) template void Chart::drawChrtValAxis() { - double slots; - int i, intv; - double cVal, cChrtRng; - char sVal[6]; - int sLen; + double axLabel; + double cVal; + // char sVal[6]; + + getdisplay().setTextColor(fgColor); if (chrtDir == 'H') { - // adjust value range to user defined data format - if (chrtDataFmt == 'T') { - if (tempFormat == "F") { - cChrtRng = chrtRng * (9 / 5); - } else { - // data steps for Kelvin and Celsius are identical and '1' - cChrtRng = chrtRng; - } - } else { - // any other data format can be converted with standard rules - cChrtRng = convertValue(chrtRng, dataBuf.getFormat(), *commonData); - } - if (useSimuData) { - // cannot use in this case, because that would change the range value to some random data - cChrtRng = chrtRng; // take SI value in this case -> need to be improved - } - - slots = valAxis / 60.0; // number of axis labels - intv = static_cast(round(cChrtRng / slots)); - i = static_cast(convertValue(chrtMin, dataBuf.getFormat(), *commonData) + intv + 0.5); // convert and round lower chart value end - LOG_DEBUG(GwLog::DEBUG, "Chart::drawChrtValAxis: chrtRng: %.2f, cChrtRng: %.2f, slots: %.2f, intv: %d, chrtMin: %.2f, chrtMid: %.2f, chrtMax: %.2f", chrtRng, cChrtRng, slots, intv, chrtMin, chrtMid, chrtMax); - - if (chrtSz == 0 && chrtDataFmt != 'W') { // full size chart -> print multiple value lines - getdisplay().setFont(&Ubuntu_Bold12pt8b); - - int loopStrt, loopEnd, loopStp; - if (chrtDataFmt == 'S' || chrtDataFmt == 'T') { - loopStrt = valAxis - 60; - loopEnd = 30; - loopStp = -60; - } else { - loopStrt = 60; - loopEnd = valAxis - 30; - loopStp = 60; - } - - for (int j = loopStrt; (loopStp > 0) ? (j < loopEnd) : (j > loopEnd); j += loopStp) { - getdisplay().drawLine(cStart.x, cStart.y + j, cStart.x + timAxis, cStart.y + j, fgColor); - - getdisplay().fillRect(cStart.x, cStart.y + j - 11, 42, 21, bgColor); // Clear small area to remove potential chart lines - String sVal = String(i); - getdisplay().setCursor((3 - sVal.length()) * 10, cStart.y + j + 7); // value right-formated - getdisplay().printf("%s", sVal); // Range value - - i += intv; - } - - } else { // half size chart or degree values -> print just edge values + middle chart line - LOG_DEBUG(GwLog::DEBUG, "Chart::drawChrtValAxis: chrtDataFmt: %c, chrtMin: %.2f, chrtMid: %.2f, chrtMax: %.2f", chrtDataFmt, chrtMin, chrtMid, chrtMax); - getdisplay().setFont(&Ubuntu_Bold10pt8b); - - // cVal = (chrtDataFmt == 'D') ? chrtMin : chrtMax; - cVal = (chrtDataFmt == 'S' || chrtDataFmt == 'T') ? chrtMax : chrtMin; - cVal = convertValue(cVal, dataBuf.getFormat(), *commonData); // value (converted) - if (useSimuData) { // dirty fix for problem that OBP60Formatter can only be used without data simulation -> returns random values in simulation mode - // cVal = (chrtDataFmt == 'D') ? chrtMin : chrtMax; // no value conversion - cVal = (chrtDataFmt == 'S' || chrtDataFmt == 'T') ? chrtMax : chrtMin; // no value conversion - } - sLen = snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); - getdisplay().fillRect(cStart.x, cStart.y + 2, 42, 16, bgColor); // Clear small area to remove potential chart lines - getdisplay().setCursor(cStart.x + ((3 - sLen) * 10), cStart.y + 16); - getdisplay().printf("%s", sVal); // Range low end - - cVal = chrtMid; - cVal = convertValue(cVal, dataBuf.getFormat(), *commonData); // value (converted) - if (useSimuData) { // dirty fix for problem that OBP60Formatter can only be used without data simulation -> returns random values in simulation mode - cVal = chrtMid; // no value conversion - } - sLen = snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); - getdisplay().fillRect(cStart.x, cStart.y + (valAxis / 2) - 9, 42, 16, bgColor); // Clear small area to remove potential chart lines - getdisplay().setCursor(cStart.x + ((3 - sLen) * 10), cStart.y + (valAxis / 2) + 5); - getdisplay().printf("%s", sVal); // Range mid value - getdisplay().drawLine(cStart.x + 43, cStart.y + (valAxis / 2), cStart.x + timAxis, cStart.y + (valAxis / 2), fgColor); - - // cVal = (chrtDataFmt == 'D') ? chrtMax : chrtMin; - cVal = (chrtDataFmt == 'S' || chrtDataFmt == 'T') ? chrtMin : chrtMax; - cVal = convertValue(cVal, dataBuf.getFormat(), *commonData); // value (converted) - if (useSimuData) { // dirty fix for problem that OBP60Formatter can only be used without data simulation -> returns random values in simulation mode - // cVal = (chrtDataFmt == 'D') ? chrtMax : chrtMin; // no value conversion - cVal = (chrtDataFmt == 'S' || chrtDataFmt == 'T') ? chrtMax : chrtMin; // no value conversion - } - sLen = snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); - getdisplay().fillRect(cStart.x, cStart.y + valAxis - 16, 42, 16, bgColor); // Clear small area to remove potential chart lines - getdisplay().setCursor(cStart.x + ((3 - sLen) * 10), cStart.y + valAxis - 1); - getdisplay().printf("%s", sVal); // Range high end - getdisplay().drawLine(cStart.x + 43, cStart.y + valAxis, cStart.x + timAxis, cStart.y + valAxis, fgColor); - } - + // print buffer data name on right hand side of time axis (max. size 5 characters) getdisplay().setFont(&Ubuntu_Bold12pt8b); - drawTextRalign(cStart.x + timAxis, cStart.y - 3, dbName.substring(0, 4)); // buffer data name (max. size 4 characters) + drawTextRalign(cRoot.x + timAxis, cRoot.y - 3, dbName.substring(0, 5)); + + if (chrtSz == 0) { // full size chart + + if (chrtDataFmt == 'W') { + prntHorizThreeValueAxisLabel(&Ubuntu_Bold12pt8b); + return; + } + + // for any other data formats print multiple axis value lines on full charts + prntHorizMultiValueAxisLabel(&Ubuntu_Bold12pt8b); + return; + + } else { // half size chart -> just print edge values + middle chart line + LOG_DEBUG(GwLog::DEBUG, "Chart::drawChrtValAxis: chrtDataFmt: %c, chrtMin: %.2f, chrtMid: %.2f, chrtMax: %.2f", chrtDataFmt, chrtMin, chrtMid, chrtMax); + + prntHorizThreeValueAxisLabel(&Ubuntu_Bold10pt8b); + return; + } } else { // vertical chart - if (chrtSz == 0) { // full size chart -> use larger font - getdisplay().setFont(&Ubuntu_Bold12pt8b); - drawTextRalign(cStart.x + (valAxis * 0.42), cStart.y - 2, dbName.substring(0, 6)); // buffer data name (max. size 5 characters) + char sVal[6]; + + if (chrtSz == 0) { // full size chart + getdisplay().setFont(&Ubuntu_Bold12pt8b); // use larger font + drawTextRalign(cRoot.x + (valAxis * 0.42), cRoot.y - 2, dbName.substring(0, 6)); // print buffer data name (max. size 5 characters) } else { - getdisplay().setFont(&Ubuntu_Bold10pt8b); + getdisplay().setFont(&Ubuntu_Bold10pt8b); // use smaller font } - getdisplay().fillRect(cStart.x, cStart.y, valAxis, 2, fgColor); // top chart line + getdisplay().fillRect(cRoot.x, cRoot.y, valAxis, 2, fgColor); // top chart line cVal = chrtMin; - cVal = convertValue(cVal, dataBuf.getFormat(), *commonData); + cVal = convertValue(cVal, dbName, dbFormat, *commonData); // value (converted) if (useSimuData) { // dirty fix for problem that OBP60Formatter can only be used without data simulation -> returns random values in simulation mode cVal = chrtMin; // no value conversion } snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); - getdisplay().setCursor(cStart.x, cStart.y - 2); + getdisplay().setCursor(cRoot.x, cRoot.y - 2); getdisplay().printf("%s", sVal); // Range low end cVal = chrtMid; - cVal = convertValue(cVal, dataBuf.getFormat(), *commonData); + cVal = convertValue(cVal, dbName, dbFormat, *commonData); // value (converted) if (useSimuData) { // dirty fix for problem that OBP60Formatter can only be used without data simulation -> returns random values in simulation mode cVal = chrtMid; // no value conversion } snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); - drawTextCenter(cStart.x + (valAxis / 2), cStart.y - 9, sVal); // Range mid end + drawTextCenter(cRoot.x + (valAxis / 2), cRoot.y - 9, sVal); // Range mid end cVal = chrtMax; - cVal = convertValue(cVal, dataBuf.getFormat(), *commonData); + cVal = convertValue(cVal, dbName, dbFormat, *commonData); // value (converted) if (useSimuData) { // dirty fix for problem that OBP60Formatter can only be used without data simulation -> returns random values in simulation mode cVal = chrtMax; // no value conversion } snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); - drawTextRalign(cStart.x + valAxis - 2, cStart.y - 2, sVal); // Range high end + drawTextRalign(cRoot.x + valAxis - 2, cRoot.y - 2, sVal); // Range high end // draw vertical grid lines for each axis label for (int j = 0; j <= valAxis; j += (valAxis / 2)) { - getdisplay().drawLine(cStart.x + j, cStart.y, cStart.x + j, cStart.y + timAxis, fgColor); + getdisplay().drawLine(cRoot.x + j, cRoot.y, cRoot.x + j, cRoot.y + timAxis, fgColor); } } } @@ -612,8 +541,8 @@ void Chart::drawChrtValAxis() template void Chart::prntCurrValue(GwApi::BoatValue& currValue) { - const int xPosVal = (chrtDir == 'H') ? cStart.x + (timAxis / 2) - 56 : cStart.x + 32; - const int yPosVal = (chrtDir == 'H') ? cStart.y + valAxis - 7 : cStart.y + timAxis - 7; + const int xPosVal = (chrtDir == 'H') ? cRoot.x + (timAxis / 2) - 56 : cRoot.x + 32; + const int yPosVal = (chrtDir == 'H') ? cRoot.y + valAxis - 7 : cRoot.y + timAxis - 7; FormattedData frmtDbData = formatValue(&currValue, *commonData); String sdbValue = frmtDbData.svalue; // value as formatted string @@ -636,6 +565,28 @@ void Chart::prntCurrValue(GwApi::BoatValue& currValue) getdisplay().print(dbUnit); // Unit } +// print message for no valid data availabletemplate +template +void Chart::prntNoValidData() +{ + int pX, pY; + + getdisplay().setFont(&Ubuntu_Bold10pt8b); + + if (chrtDir == 'H') { + pX = cRoot.x + (timAxis / 2); + pY = cRoot.y + (valAxis / 2) - 10; + } else { + pX = cRoot.x + (valAxis / 2); + pY = cRoot.y + (timAxis / 2) - 10; + } + + getdisplay().fillRect(pX - 37, pY - 10, 78, 24, bgColor); // Clear area for message + drawTextCenter(pX, pY, "No data"); + + LOG_DEBUG(GwLog::LOG, "Page chart <%s>: No valid data available", dbName); +} + // Get maximum difference of last of dataBuf ringbuffer values to center chart; for angle data only template double Chart::getAngleRng(double center, size_t amount) @@ -672,6 +623,170 @@ double Chart::getAngleRng(double center, size_t amount) return (maxRng != dbMIN_VAL ? maxRng : dbMAX_VAL); // Return range from to } +// print horizontal axis label with only three values: top, mid, and bottom +template +void Chart::prntHorizThreeValueAxisLabel(const GFXfont* font) +{ + double axLabel; + double chrtMin, chrtMid, chrtMax; + int xOffset, yOffset; // offset for text position of x axis label for different font sizes + String sVal; + + if (font == &Ubuntu_Bold10pt8b) { + xOffset = 39; + yOffset = 15; + } else if (font == &Ubuntu_Bold12pt8b) { + xOffset = 51; + yOffset = 17; + } + getdisplay().setFont(font); + + // convert & round chart bottom+top label to next range step + chrtMin = convertValue(this->chrtMin, dbName, dbFormat, *commonData); + chrtMid = convertValue(this->chrtMid, dbName, dbFormat, *commonData); + chrtMax = convertValue(this->chrtMax, dbName, dbFormat, *commonData); + chrtMin = std::round(chrtMin * 100.0) / 100.0; + chrtMid = std::round(chrtMid * 100.0) / 100.0; + chrtMax = std::round(chrtMax * 100.0) / 100.0; + + // print top axis label + axLabel = (chrtDataFmt == 'S' || chrtDataFmt == 'T') ? chrtMax : chrtMin; + sVal = formatLabel(axLabel); + getdisplay().fillRect(cRoot.x, cRoot.y + 2, xOffset + 4, yOffset, bgColor); // Clear small area to remove potential chart lines + drawTextRalign(cRoot.x + xOffset, cRoot.y + yOffset, sVal); // range value + + // print mid axis label + axLabel = chrtMid; + sVal = formatLabel(axLabel); + getdisplay().fillRect(cRoot.x, cRoot.y + (valAxis / 2) - 9, xOffset + 4, 16, bgColor); // Clear small area to remove potential chart lines + drawTextRalign(cRoot.x + xOffset, cRoot.y + (valAxis / 2) + 5, sVal); // range value + getdisplay().drawLine(cRoot.x + xOffset + 4, cRoot.y + (valAxis / 2), cRoot.x + timAxis, cRoot.y + (valAxis / 2), fgColor); + + // print bottom axis label + axLabel = (chrtDataFmt == 'S' || chrtDataFmt == 'T') ? chrtMin : chrtMax; + sVal = formatLabel(axLabel); + getdisplay().fillRect(cRoot.x, cRoot.y + valAxis - 16, xOffset + 3, 16, bgColor); // Clear small area to remove potential chart lines + drawTextRalign(cRoot.x + xOffset, cRoot.y + valAxis, sVal); // range value + getdisplay().drawLine(cRoot.x + xOffset + 2, cRoot.y + valAxis, cRoot.x + timAxis, cRoot.y + valAxis, fgColor); +} + +// print horizontal axis label with multiple axis lines +template +void Chart::prntHorizMultiValueAxisLabel(const GFXfont* font) +{ + double chrtMin, chrtMax, chrtRng; + double axSlots, axIntv, axLabel; + int xOffset; // offset for text position of x axis label for different font sizes + String sVal; + + if (font == &Ubuntu_Bold10pt8b) { + xOffset = 38; + } else if (font == &Ubuntu_Bold12pt8b) { + xOffset = 50; + } + getdisplay().setFont(font); + + chrtMin = convertValue(this->chrtMin, dbName, dbFormat, *commonData); + // chrtMin = std::floor(chrtMin / rngStep) * rngStep; + chrtMin = std::round(chrtMin * 100.0) / 100.0; + chrtMax = convertValue(this->chrtMax, dbName, dbFormat, *commonData); + // chrtMax = std::ceil(chrtMax / rngStep) * rngStep; + chrtMax = std::round(chrtMax * 100.0) / 100.0; + chrtRng = std::round((chrtMax - chrtMin) * 100) / 100; + + axSlots = valAxis / static_cast(VALAXIS_STEP); // number of axis labels (and we want to have a double calculation, no integer) + axIntv = chrtRng / axSlots; + axLabel = chrtMin + axIntv; + LOG_DEBUG(GwLog::DEBUG, "Chart::printHorizMultiValueAxisLabel: chrtRng: %.2f, th-chrtRng: %.2f, axSlots: %.2f, axIntv: %.2f, axLabel: %.2f, chrtMin: %.2f, chrtMid: %.2f, chrtMax: %.2f", chrtRng, this->chrtRng, axSlots, axIntv, axLabel, this->chrtMin, chrtMid, chrtMax); + + int loopStrt, loopEnd, loopStp; + if (chrtDataFmt == 'S' || chrtDataFmt == 'T' || chrtDataFmt == 'O') { + // High value at top + loopStrt = valAxis - VALAXIS_STEP; + loopEnd = VALAXIS_STEP / 2; + loopStp = VALAXIS_STEP * -1; + } else { + // Low value at top + loopStrt = VALAXIS_STEP; + loopEnd = valAxis - (VALAXIS_STEP / 2); + loopStp = VALAXIS_STEP; + } + + for (int j = loopStrt; (loopStp > 0) ? (j < loopEnd) : (j > loopEnd); j += loopStp) { + sVal = formatLabel(axLabel); + // sVal = convNformatLabel(axLabel); + getdisplay().fillRect(cRoot.x, cRoot.y + j - 11, xOffset + 4, 21, bgColor); // Clear small area to remove potential chart lines + drawTextRalign(cRoot.x + xOffset, cRoot.y + j + 7, sVal); // range value + getdisplay().drawLine(cRoot.x + xOffset + 3, cRoot.y + j, cRoot.x + timAxis, cRoot.y + j, fgColor); + + axLabel += axIntv; + } +} + +// Draw chart line with thickness of 2px +template +void Chart::drawBoldLine(int16_t x1, int16_t y1, int16_t x2, int16_t y2) +{ + + int16_t dx = std::abs(x2 - x1); + int16_t dy = std::abs(y2 - y1); + + getdisplay().drawLine(x1, y1, x2, y2, fgColor); + + if (dx >= dy) { // line has horizontal tendency + getdisplay().drawLine(x1, y1 - 1, x2, y2 - 1, fgColor); + } else { // line has vertical tendency + getdisplay().drawLine(x1 - 1, y1, x2 - 1, y2, fgColor); + } +} + +// Convert and format current axis label to user defined format; helper function for easier handling of OBP60Formatter +template +String Chart::convNformatLabel(double label) +{ + GwApi::BoatValue tmpBVal(dbName); // temporary boat value for string formatter + String sVal; + + tmpBVal.setFormat(dbFormat); + tmpBVal.valid = true; + tmpBVal.value = label; + sVal = formatValue(&tmpBVal, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + if (sVal.length() > 0 && sVal[0] == '!') { + sVal = sVal.substring(1); // cut leading "!" created at OBPFormatter for use with other font than 7SEG + } + + return sVal; +} + +// Format current axis label for printing w/o data format conversion (has been done earlier) +template +String Chart::formatLabel(const double& label) +{ + char sVal[11]; + + if (dbFormat == "formatCourse" || dbFormat == "formatWind") { + // Format 3 numbers with prefix zero + snprintf(sVal, sizeof(sVal), "%03.0f", label); + + } else if (dbFormat == "formatRot") { + if (label > -10 && label < 10) { + snprintf(sVal, sizeof(sVal), "%3.2f", label); + } else { + snprintf(sVal, sizeof(sVal), "%3.0f", label); + } + } + + else { + if (label < 10) { + snprintf(sVal, sizeof(sVal), "%3.1f", label); + } else { + snprintf(sVal, sizeof(sVal), "%3.0f", label); + } + } + + return String(sVal); +} + // Explicitly instantiate class with required data types to avoid linker errors template class Chart; // --- Class Chart --------------- diff --git a/lib/obp60task/OBPcharts.h b/lib/obp60task/OBPcharts.h index 9ce3017..db62a3c 100644 --- a/lib/obp60task/OBPcharts.h +++ b/lib/obp60task/OBPcharts.h @@ -1,16 +1,24 @@ // Function lib for display of boat data in various graphical chart formats #pragma once #include "Pagedata.h" +#include "OBP60Extensions.h" struct Pos { int x; int y; }; -template class RingBuffer; +struct ChartProps { + double range; + double step; +}; + +template +class RingBuffer; class GwLog; -template class Chart { +template +class Chart { protected: CommonData* commonData; GwLog* logger; @@ -25,14 +33,14 @@ protected: String tempFormat; // user defined format for temperature double zeroValue; // "0" SI value for temperature + int dWidth; // Display width + int dHeight; // Display height int top = 44; // chart gap at top of display (25 lines for standard gap + 19 lines for axis labels) int bottom = 25; // chart gap at bottom of display to keep space for status line int hGap = 11; // gap between 2 horizontal charts; actual gap is 2x int vGap = 17; // gap between 2 vertical charts; actual gap is 2x - int dWidth; // Display width - int dHeight; // Display height int timAxis, valAxis; // size of time and value chart axis - Pos cStart; // start point of chart area + Pos cRoot; // start point of chart area double chrtRng; // Range of buffer values from min to max value double chrtMin; // Range low end value double chrtMax; // Range high end value @@ -60,27 +68,39 @@ protected: int x, y; // x and y coordinates for drawing int prevX, prevY; // Last x and y coordinates for drawing - void drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue); // Draw chart line + static constexpr int8_t MIN_FREE_VALUES = 60; + static constexpr int8_t THRESHOLD_NO_DATA = 3; + static constexpr int8_t VALAXIS_STEP = 60; + + void drawChrt(int8_t& chrtIntv, GwApi::BoatValue& currValue); // Draw chart line + void getBufStartNSize(int8_t& chrtIntv); // Identify buffer size and buffer start position for chart void calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, double& rng); // Calculate chart points for value axis and return range between and - void drawChrtTimeAxis(int8_t chrtIntv); // Draw time axis of chart, value and lines + void drawChrtTimeAxis(int8_t& chrtIntv); // Draw time axis of chart, value and lines void drawChrtValAxis(); // Draw value axis of chart, value and lines void prntCurrValue(GwApi::BoatValue& currValue); // Add current boat data value to chart + void prntNoValidData(); // print message for no valid data available double getAngleRng(double center, size_t amount); // Calculate range between chart center and edges + void prntHorizThreeValueAxisLabel(const GFXfont* font); // print horizontal axis label with only three values: top, mid, and bottom + void prntHorizMultiValueAxisLabel(const GFXfont* font); // print horizontal axis label with multiple axis lines + void drawBoldLine(int16_t x1, int16_t y1, int16_t x2, int16_t y2); // Draw chart line with thickness of 2px + String convNformatLabel(double label); // Convert and format current axis label to user defined format; helper function for easier handling of OBP60Formatter + String formatLabel(const double& label); // Format current axis label for printing w/o data format conversion (has been done earlier) public: - // Define default chart range for each boat data type - static std::map dfltChrtRng; + // Define default chart range and range step for each boat data type + static std::map dfltChrtDta; Chart(RingBuffer& dataBuf, char chrtDir, int8_t chrtSz, double dfltRng, CommonData& common, bool useSimuData); // Chart object of data chart ~Chart(); - void showChrt(GwApi::BoatValue currValue, int8_t chrtIntv, bool showCurrValue); // Perform all actions to draw chart + void showChrt(GwApi::BoatValue currValue, int8_t& chrtIntv, bool showCurrValue); // Perform all actions to draw chart }; template -std::map Chart::dfltChrtRng = { - { "formatWind", 60.0 * DEG_TO_RAD }, // default course range 60 degrees - { "formatCourse", 60.0 * DEG_TO_RAD }, // default course range 60 degrees - { "formatKnots", 5.1 }, // default speed range in m/s - { "formatDepth", 15.0 }, // default depth range in m - { "kelvinToC", 30.0 } // default temp range in °C/K +std::map Chart::dfltChrtDta = { + { "formatWind", { 60.0 * DEG_TO_RAD, 10.0 * DEG_TO_RAD } }, // default course range 60 degrees + { "formatCourse", { 60.0 * DEG_TO_RAD, 10.0 * DEG_TO_RAD } }, // default course range 60 degrees + //{ "formatKnots", { 7.71, 2.57 } }, // default speed range in m/s + { "formatKnots", { 7.71, 2.56 } }, // default speed range in m/s + { "formatDepth", { 15.0, 5.0 } }, // default depth range in m + { "kelvinToC", { 30.0, 5.0 } } // default temp range in °C/K }; \ No newline at end of file diff --git a/lib/obp60task/PageOneValue.cpp b/lib/obp60task/PageOneValue.cpp index a9ce6ff..57175f1 100644 --- a/lib/obp60task/PageOneValue.cpp +++ b/lib/obp60task/PageOneValue.cpp @@ -15,8 +15,8 @@ private: bool keylock = false; // Keylock char pageMode = 'V'; // Page mode: 'V' for value, 'C' for chart, 'B' for both - int dataIntv = 1; // Update interval for wind history chart: - // (1)|(2)|(3)|(4)|(8) x 240 seconds for 4, 8, 12, 16, 32 min. history chart + int8_t dataIntv = 1; // Update interval for wind history chart: + // (1)|(2)|(3)|(4)|(8) x 240 seconds for 4, 8, 12, 16, 32 min. history chart //String lengthformat; bool useSimuData; @@ -209,8 +209,8 @@ public: dataHstryBuf = pageData.hstryBuffers->getBuffer(bValName1); if (dataHstryBuf) { - dataFlChart.reset(new Chart(*dataHstryBuf, 'H', 0, Chart::dfltChrtRng[bValFormat], *commonData, useSimuData)); - dataHfChart.reset(new Chart(*dataHstryBuf, 'H', 2, Chart::dfltChrtRng[bValFormat], *commonData, useSimuData)); + dataFlChart.reset(new Chart(*dataHstryBuf, 'H', 0, Chart::dfltChrtDta[bValFormat].range, *commonData, useSimuData)); + dataHfChart.reset(new Chart(*dataHstryBuf, 'H', 2, Chart::dfltChrtDta[bValFormat].range, *commonData, useSimuData)); LOG_DEBUG(GwLog::DEBUG, "PageOneValue: Created chart objects for %s", bValName1); } else { LOG_DEBUG(GwLog::DEBUG, "PageOneValue: No chart objects available for %s", bValName1); @@ -224,8 +224,6 @@ public: { LOG_DEBUG(GwLog::LOG, "Display PageOneValue"); - //GwConfigHandler* config = commonData->config; - //GwLog* logger = commonData->logger; // Get boat value for page GwApi::BoatValue* bValue1 = pageData.values[0]; // Page boat data element @@ -236,24 +234,8 @@ public: setFlashLED(false); } -/* if (!dataFlChart) { // Create chart objects if they don't exist - GwApi::BoatValue* bValue1 = pageData.values[0]; // Page boat data element - String bValName1 = bValue1->getName(); // Value name - String bValFormat = bValue1->getFormat(); // Value format - - dataHstryBuf = pageData.hstryBuffers->getBuffer(bValName1); - - if (dataHstryBuf) { - dataFlChart.reset(new Chart(*dataHstryBuf, 'H', 0, Chart::dfltChrtRng[bValFormat], *commonData, useSimuData)); - dataHfChart.reset(new Chart(*dataHstryBuf, 'H', 2, Chart::dfltChrtRng[bValFormat], *commonData, useSimuData)); - LOG_DEBUG(GwLog::DEBUG, "PageOneValue: Created chart objects for %s", bValName1); - } else { - LOG_DEBUG(GwLog::DEBUG, "PageOneValue: No chart objects available for %s", bValName1); - } - } */ - if (bValue1 == NULL) - return PAGE_OK; // no data, no display of page + return PAGE_OK; // no data, no page to display LOG_DEBUG(GwLog::DEBUG, "PageOneValue: printing %s, %.3f", bValue1->getName().c_str(), bValue1->value); diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 50da88f..49d11d3 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -19,8 +19,8 @@ private: bool showTruW = true; // Show true wind or apparent wind in chart area bool oldShowTruW = false; // remember recent user selection of wind data type - int dataIntv = 1; // Update interval for wind history chart: - // (1)|(2)|(3)|(4)|(8) x 240 seconds for 4, 8, 12, 16, 32 min. history chart + int8_t dataIntv = 1; // Update interval for wind history chart: + // (1)|(2)|(3)|(4)|(8) x 240 seconds for 4, 8, 12, 16, 32 min. history chart bool useSimuData; // bool holdValues; String flashLED; @@ -50,9 +50,6 @@ private: GwApi::BoatValue* wdBVal = nullptr; GwApi::BoatValue* wsBVal = nullptr; - const double dfltRngWd = 60.0 * DEG_TO_RAD; // default range for course chart from min to max value in RAD - const double dfltRngWs = 7.5; // default range for wind speed chart from min to max value in m/s - public: PageWindPlot(CommonData& common) { @@ -198,12 +195,12 @@ public: twsHstry = pageData.hstryBuffers->getBuffer("TWS"); if (twdHstry) { - twdFlChart.reset(new Chart(*twdHstry, 'V', 0, dfltRngWd, *commonData, useSimuData)); - twdHfChart.reset(new Chart(*twdHstry, 'V', 1, dfltRngWd, *commonData, useSimuData)); + twdFlChart.reset(new Chart(*twdHstry, 'V', 0, Chart::dfltChrtDta["formatCourse"].range, *commonData, useSimuData)); + twdHfChart.reset(new Chart(*twdHstry, 'V', 1, Chart::dfltChrtDta["formatCourse"].range, *commonData, useSimuData)); } if (twsHstry) { - twsFlChart.reset(new Chart(*twsHstry, 'H', 0, dfltRngWs, *commonData, useSimuData)); - twsHfChart.reset(new Chart(*twsHstry, 'V', 2, dfltRngWs, *commonData, useSimuData)); + twsFlChart.reset(new Chart(*twsHstry, 'H', 0, Chart::dfltChrtDta["formatKnots"].range, *commonData, useSimuData)); + twsHfChart.reset(new Chart(*twsHstry, 'V', 2, Chart::dfltChrtDta["formatKnots"].range, *commonData, useSimuData)); } } @@ -212,12 +209,12 @@ public: awsHstry = pageData.hstryBuffers->getBuffer("AWS"); if (awdHstry) { - awdFlChart.reset(new Chart(*awdHstry, 'V', 0, dfltRngWd, *commonData, useSimuData)); - awdHfChart.reset(new Chart(*awdHstry, 'V', 1, dfltRngWd, *commonData, useSimuData)); + awdFlChart.reset(new Chart(*awdHstry, 'V', 0, Chart::dfltChrtDta["formatCourse"].range, *commonData, useSimuData)); + awdHfChart.reset(new Chart(*awdHstry, 'V', 1, Chart::dfltChrtDta["formatCourse"].range, *commonData, useSimuData)); } if (awsHstry) { - awsFlChart.reset(new Chart(*awsHstry, 'H', 0, dfltRngWs, *commonData, useSimuData)); - awsHfChart.reset(new Chart(*awsHstry, 'V', 2, dfltRngWs, *commonData, useSimuData)); + awsFlChart.reset(new Chart(*awsHstry, 'H', 0, Chart::dfltChrtDta["formatKnots"].range, *commonData, useSimuData)); + awsHfChart.reset(new Chart(*awsHstry, 'V', 2, Chart::dfltChrtDta["formatKnots"].range, *commonData, useSimuData)); } if (twdHstry && twsHstry && awdHstry && awsHstry) { LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Created wind charts"); diff --git a/lib/obp60task/Pagedata.h b/lib/obp60task/Pagedata.h index d6f342e..79d1ad9 100644 --- a/lib/obp60task/Pagedata.h +++ b/lib/obp60task/Pagedata.h @@ -207,3 +207,6 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata); // Helper method for conversion of any data value from SI to user defined format (defined in OBP60Formatter) double convertValue(const double &value, const String &format, CommonData &commondata); +double convertValue(const double &value, const String &name, const String &format, CommonData &commondata); +// Helper method for conversion of boat data values from user defined format to SI (defined in OBP60Formatter) +double convertToSItemp(const double &value, const String &name, const String &format, CommonData &commondata);