468 using OutputFunc = std::function<void(
const std::string &)>;
483 std::string className;
488 static constexpr size_t kNoAxes =
static_cast<size_t>(-1);
489 size_t axesIndex = 0;
492 static constexpr size_t kNoDataset =
static_cast<size_t>(-1);
493 size_t datasetIndex = kNoDataset;
500 std::map<std::string, std::string> props;
501 bool deleted =
false;
503 bool isChart()
const {
return datasetIndex != kNoDataset; }
508 int createHandle(std::string type, std::string className,
509 size_t datasetIndex = HandleRecord::kNoDataset,
510 std::string slot =
"")
512 auto &fig = current();
513 return createHandleAt(currentFigure_,
514 static_cast<size_t>(fig.currentAxes),
515 std::move(type), std::move(className),
516 std::move(slot), datasetIndex);
522 int createHandleAt(
int figureId,
size_t axesIndex, std::string type,
523 std::string className, std::string slot =
"",
524 size_t datasetIndex = HandleRecord::kNoDataset)
526 const int id = nextHandleId_++;
528 rec.type = std::move(type);
529 rec.className = std::move(className);
530 rec.figureId = figureId;
531 rec.axesIndex = axesIndex;
532 rec.slot = std::move(slot);
533 rec.datasetIndex = datasetIndex;
534 handleRegistry_[id] = std::move(rec);
538 HandleRecord *findHandle(
int id)
540 auto it = handleRegistry_.find(
id);
541 if (it == handleRegistry_.end() || it->second.deleted)
546 void deleteHandle(
int id)
548 auto it = handleRegistry_.find(
id);
549 if (it == handleRegistry_.end() || it->second.deleted)
551 it->second.deleted =
true;
552 if (!it->second.isChart())
557 DatasetInfo *ds = datasetOf(it->second);
560 auto fit = figures_.find(it->second.figureId);
561 if (fit == figures_.end() || it->second.axesIndex >= fit->second.axes.size())
563 auto &ax = fit->second.axes[it->second.axesIndex];
564 if (it->second.datasetIndex >= ax.datasets.size())
566 ax.datasets.erase(ax.datasets.begin() + it->second.datasetIndex);
567 fit->second.modified =
true;
568 for (
auto &[hid, rec] : handleRegistry_) {
569 if (!rec.deleted && rec.figureId == it->second.figureId
570 && rec.axesIndex == it->second.axesIndex
571 && rec.datasetIndex > it->second.datasetIndex)
578 void removeAxes(
int figureId,
size_t axesIndex)
580 auto fit = figures_.find(figureId);
581 if (fit == figures_.end() || axesIndex >= fit->second.axes.size())
583 for (
auto &[hid, rec] : handleRegistry_)
584 if (!rec.deleted && rec.figureId == figureId
585 && rec.axesIndex == axesIndex)
587 fit->second.axes.erase(fit->second.axes.begin() + axesIndex);
588 if (fit->second.currentAxes >= (
int)axesIndex
589 && fit->second.currentAxes > 0)
590 --fit->second.currentAxes;
591 for (
auto &[hid, rec] : handleRegistry_) {
592 if (!rec.deleted && rec.figureId == figureId
593 && rec.axesIndex > axesIndex)
596 fit->second.modified =
true;
605 auto fit = figures_.find(rec.figureId);
606 if (fit == figures_.end() || rec.axesIndex >= fit->second.axes.size())
608 auto &ax = fit->second.axes[rec.axesIndex];
609 if (rec.datasetIndex >= ax.datasets.size())
611 return &ax.datasets[rec.datasetIndex];
616 auto fit = figures_.find(rec.figureId);
617 if (fit == figures_.end() || rec.axesIndex >= fit->second.axes.size())
619 return &fit->second.axes[rec.axesIndex];
622 FigureState *figureOf(
int figureId)
624 auto it = figures_.find(figureId);
625 return it == figures_.end() ? nullptr : &it->second;
628 void markFigureModified(
int figureId)
630 auto fit = figures_.find(figureId);
631 if (fit != figures_.end())
632 fit->second.modified =
true;
645 void invalidateHandles(
int figureId,
int axesIdx,
646 bool includeAxesRecord,
bool includeFigureRecord)
648 for (
auto &[hid, rec] : handleRegistry_) {
649 if (rec.figureId != figureId || rec.deleted)
651 if (axesIdx >= 0 && rec.axesIndex != (
size_t)axesIdx)
653 if (!includeFigureRecord && rec.type ==
"figure")
655 if (!includeAxesRecord && rec.type ==
"axes")
661 std::unordered_map<int, HandleRecord> &handles() {
return handleRegistry_; }
671 if (figures_.find(currentFigure_) == figures_.end()) {
673 fs.id = currentFigure_;
675 figures_[currentFigure_] = fs;
677 return figures_[currentFigure_];
694 static constexpr int kSeriesPreviewCols = 2000;
699 if (ax.yyEnabled && ds.yside.empty())
700 ds.yside = ax.activeYside;
701 maybeDownsampleSeries(ds);
702 ax.datasets.push_back(std::move(ds));
712 if (ds.type !=
"line" && ds.type !=
"stairs")
return;
713 if (ds.xJson.empty() || ds.yJson.empty())
return;
715 auto xr = figdetail::parseFlatDoubles(ds.xJson);
716 auto yr = figdetail::parseFlatDoubles(ds.yJson);
717 const std::size_t n = xr.size();
721 for (std::size_t i = 1; i < n; ++i)
if (xr[i] < xr[i - 1])
return;
722 double xlo = xr.front(), xhi = xr.back();
723 if (xhi < xlo) std::swap(xlo, xhi);
724 double ylo = INFINITY, yhi = -INFINITY;
725 for (
double v : yr)
if (std::isfinite(v)) { ylo = std::min(ylo, v); yhi = std::max(yhi, v); }
726 if (!std::isfinite(ylo)) { ylo = 0; yhi = 0; }
727 auto prev = decimateM4(xr.data(), yr.data(), n, xlo, xhi, kSeriesPreviewCols);
728 ds.xJson = figdetail::serializeFlatDoubles(prev.x);
729 ds.yJson = figdetail::serializeFlatDoubles(prev.y);
730 ds.xRaw = std::move(xr);
731 ds.yRaw = std::move(yr);
732 ds.seriesDownsampled =
true;
734 ds.sxLo = xlo; ds.sxHi = xhi; ds.syLo = ylo; ds.syHi = yhi;
740 while (figures_.find(
id) != figures_.end())
745 fs.axes.push_back(AxesState{});
753 if (figures_.find(n) == figures_.end()) {
756 fs.axes.push_back(AxesState{});
762 int currentFigureId()
const {
return currentFigure_; }
767 auto &fig = current();
772 for (
int i = 0; i < static_cast<int>(fig.axes.size()); ++i) {
773 if (fig.axes[i].subplotIndex == p) {
783 for (
int i = 0; i < static_cast<int>(fig.axes.size()); ++i) {
784 if (fig.axes[i].subplotIndex == 0) {
785 invalidateHandles(fig.id, i,
788 fig.axes[i].subplotIndex = p;
797 fig.axes.push_back(ax);
798 fig.currentAxes =
static_cast<int>(fig.axes.size()) - 1;
801 void prepareForPlot()
803 auto &fig = current();
804 auto &ax = fig.cur();
809 invalidateHandles(fig.id, fig.currentAxes,
817 int savedSubplot = ax.subplotIndex;
818 std::string savedColorScale = ax.colorScale;
820 ax.subplotIndex = savedSubplot;
821 ax.colorScale = savedColorScale;
832 void beginChartCall() { chartCallBase_ =
currentAxes().datasets.size(); }
833 size_t chartCallBase()
const {
return chartCallBase_; }
838 for (
auto &[
id, fig] : figures_) {
841 fig.modified =
false;
843 std::ostringstream os;
844 os <<
"__FIGURE_DATA__:{\"id\":" << fig.id;
847 if (fig.subplotRows > 0) {
848 os <<
",\"subplotGrid\":[" << fig.subplotRows <<
"," << fig.subplotCols <<
"]";
850 if (!fig.linkMode.empty())
851 os <<
",\"linkMode\":\"" << fig.linkMode <<
"\"";
852 if (!fig.superTitle.empty())
853 os <<
",\"superTitle\":\"" << jsonEscapeFig(fig.superTitle) <<
"\"";
854 if (!fig.annotations.empty()) {
855 os <<
",\"annotations\":[";
856 for (
size_t ai = 0; ai < fig.annotations.size(); ++ai) {
857 const auto &an = fig.annotations[ai];
859 os <<
"{\"type\":\"" << an.type <<
"\""
860 <<
",\"x\":[" << an.x1 <<
',' << an.x2 <<
"]"
861 <<
",\"y\":[" << an.y1 <<
',' << an.y2 <<
"]"
862 <<
",\"text\":\"" << jsonEscapeFig(an.text) <<
"\"";
863 if (!an.colorHex.empty())
864 os <<
",\"color\":\"" << an.colorHex <<
"\"";
865 if (!an.faceHex.empty())
866 os <<
",\"face\":\"" << an.faceHex <<
"\"";
867 if (an.fontSize != 10)
868 os <<
",\"fontSize\":" << an.fontSize;
875 for (
size_t ai = 0; ai < fig.axes.size(); ++ai) {
878 auto &ax = fig.axes[ai];
880 if (ax.subplotIndex > 0)
881 os <<
"\"subplotIndex\":" << ax.subplotIndex <<
",";
882 os <<
"\"datasets\":[";
883 for (
size_t i = 0; i < ax.datasets.size(); ++i) {
886 auto &ds = ax.datasets[i];
887 os <<
"{\"x\":" << ds.xJson <<
",\"y\":" << ds.yJson <<
",\"type\":\""
889 if (ds.seriesDownsampled)
890 os <<
",\"seriesDownsampled\":true,\"n\":" << ds.seriesN
891 <<
",\"xRange\":[" << ds.sxLo <<
"," << ds.sxHi <<
"]"
892 <<
",\"yRange\":[" << ds.syLo <<
"," << ds.syHi <<
"]";
893 if (!ds.label.empty())
894 os <<
",\"label\":\"" << jsonEscapeFig(ds.label) <<
"\"";
895 if (!ds.style.empty())
896 os <<
",\"style\":\"" << ds.style <<
"\"";
897 if (ds.lineWidth > 0)
898 os <<
",\"lineWidth\":" << ds.lineWidth;
899 if (ds.markerSize > 0)
900 os <<
",\"markerSize\":" << ds.markerSize;
902 os <<
",\"visible\":false";
908 if (!ds.sizeJson.empty())
909 os <<
",\"size\":" << ds.sizeJson;
910 if (!ds.colorJson.empty())
911 os <<
",\"pointColor\":" << ds.colorJson;
912 if (!ds.eJson.empty())
913 os <<
",\"e\":" << ds.eJson;
914 if (!ds.eNegJson.empty())
915 os <<
",\"eNeg\":" << ds.eNegJson;
916 if (!ds.ePosJson.empty())
917 os <<
",\"ePos\":" << ds.ePosJson;
918 if (!ds.uJson.empty())
919 os <<
",\"u\":" << ds.uJson;
920 if (!ds.vJson.empty())
921 os <<
",\"v\":" << ds.vJson;
922 if (!ds.yside.empty())
923 os <<
",\"yside\":\"" << ds.yside <<
"\"";
924 if (!ds.zJson.empty())
925 os <<
",\"z\":" << ds.zJson;
926 if (!ds.vertexColorsJson.empty())
927 os <<
",\"vertexColors\":" << ds.vertexColorsJson;
928 if (!ds.rgbJson.empty()) {
929 os <<
",\"rgb\":" << ds.rgbJson;
930 os <<
",\"originalRows\":" << ds.originalRows
931 <<
",\"originalCols\":" << ds.originalCols;
933 os <<
",\"downsampled\":true";
935 if (!ds.zQuantized.empty()) {
936 os <<
",\"cminOrig\":" << ds.cminOrig
937 <<
",\"cmaxOrig\":" << ds.cmaxOrig;
938 if (ds.colorScaleBaked)
939 os <<
",\"colorScaleBaked\":\"log\"";
940 os <<
",\"originalRows\":" << ds.originalRows
941 <<
",\"originalCols\":" << ds.originalCols;
943 os <<
",\"downsampled\":true";
947 os <<
"],\"config\":{";
948 os <<
"\"title\":\"" << jsonEscapeFig(ax.title) <<
"\"";
949 if (!ax.subtitle.empty())
950 os <<
",\"subtitle\":\"" << jsonEscapeFig(ax.subtitle) <<
"\"";
951 os <<
",\"xlabel\":\"" << jsonEscapeFig(ax.xlabel) <<
"\"";
952 os <<
",\"ylabel\":\"" << jsonEscapeFig(ax.ylabel) <<
"\"";
953 if (!ax.xlimJson.empty())
954 os <<
",\"xlim\":" << ax.xlimJson;
955 if (!ax.ylimJson.empty())
956 os <<
",\"ylim\":" << ax.ylimJson;
957 if (!ax.rlimJson.empty())
958 os <<
",\"rlim\":" << ax.rlimJson;
959 if (!ax.thetalimJson.empty())
960 os <<
",\"thetalim\":" << ax.thetalimJson;
961 if (!ax.climJson.empty())
962 os <<
",\"clim\":" << ax.climJson;
963 if (!ax.colormapName.empty())
964 os <<
",\"colormap\":\"" << ax.colormapName <<
"\"";
965 if (!ax.customColormapJson.empty())
966 os <<
",\"customColormap\":" << ax.customColormapJson;
971 if (ax.gridUserTouched)
972 os <<
",\"grid\":\"" << (ax.gridMajor ?
"on" :
"off") <<
"\"";
974 os <<
",\"gridMinor\":\"on\"";
975 os <<
",\"polar\":" << (ax.polar ?
"true" :
"false");
976 os <<
",\"xscale\":\"" << ax.xscale <<
"\"";
977 os <<
",\"yscale\":\"" << ax.yscale <<
"\"";
978 if (!ax.axisMode.empty())
979 os <<
",\"axisMode\":\"" << ax.axisMode <<
"\"";
980 if (!ax.xTicksJson.empty())
981 os <<
",\"xticks\":" << ax.xTicksJson;
982 if (!ax.yTicksJson.empty())
983 os <<
",\"yticks\":" << ax.yTicksJson;
984 if (!ax.zTicksJson.empty())
985 os <<
",\"zticks\":" << ax.zTicksJson;
986 if (!ax.xTickLabelsJson.empty())
987 os <<
",\"xticklabels\":" << ax.xTickLabelsJson;
988 if (!ax.yTickLabelsJson.empty())
989 os <<
",\"yticklabels\":" << ax.yTickLabelsJson;
990 if (!ax.zTickLabelsJson.empty())
991 os <<
",\"zticklabels\":" << ax.zTickLabelsJson;
992 if (!ax.xTickFormat.empty())
993 os <<
",\"xtickformat\":\"" << jsonEscapeFig(ax.xTickFormat) <<
"\"";
994 if (!ax.yTickFormat.empty())
995 os <<
",\"ytickformat\":\"" << jsonEscapeFig(ax.yTickFormat) <<
"\"";
996 if (!ax.zTickFormat.empty())
997 os <<
",\"ztickformat\":\"" << jsonEscapeFig(ax.zTickFormat) <<
"\"";
998 if (ax.xTickAngle != 0.0)
999 os <<
",\"xtickangle\":" << ax.xTickAngle;
1000 if (ax.yTickAngle != 0.0)
1001 os <<
",\"ytickangle\":" << ax.yTickAngle;
1004 if (ax.fontSize != 10.0)
1005 os <<
",\"fontSize\":" << ax.fontSize;
1006 if (ax.fontWeight !=
"normal")
1007 os <<
",\"fontWeight\":\"" << ax.fontWeight <<
"\"";
1008 if (ax.fontName !=
"Helvetica")
1009 os <<
",\"fontName\":\"" << jsonEscapeFig(ax.fontName) <<
"\"";
1010 if (ax.lineWidth != 0.5)
1011 os <<
",\"axesLineWidth\":" << ax.lineWidth;
1014 if (!ax.axisVisible)
1015 os <<
",\"axisVisible\":false";
1017 os <<
",\"box\":\"off\"";
1018 if (ax.xDir ==
"reverse")
1019 os <<
",\"xDir\":\"reverse\"";
1020 if (ax.yDir ==
"reverse")
1021 os <<
",\"yDir\":\"reverse\"";
1023 os <<
",\"thetaDir\":\"" << ax.thetaDir <<
"\"";
1024 os <<
",\"thetaZeroLocation\":\"" << ax.thetaZeroLocation <<
"\"";
1025 if (!ax.thetaticksJson.empty())
1026 os <<
",\"thetaticks\":" << ax.thetaticksJson;
1027 if (!ax.rticksJson.empty())
1028 os <<
",\"rticks\":" << ax.rticksJson;
1029 if (!ax.thetaticklabelsJson.empty())
1030 os <<
",\"thetaticklabels\":" << ax.thetaticklabelsJson;
1031 if (!ax.rticklabelsJson.empty())
1032 os <<
",\"rticklabels\":" << ax.rticklabelsJson;
1034 if (!ax.legendLabels.empty()) {
1035 os <<
",\"legend\":[";
1036 for (
size_t i = 0; i < ax.legendLabels.size(); ++i) {
1039 os <<
"\"" << jsonEscapeFig(ax.legendLabels[i]) <<
"\"";
1043 if (ax.legendHidden)
1044 os <<
",\"legendHidden\":true";
1045 if (!ax.legendLocation.empty())
1046 os <<
",\"legendLocation\":\"" << ax.legendLocation <<
"\"";
1047 if (!ax.legendBoxOn)
1048 os <<
",\"legendBox\":\"off\"";
1049 if (!ax.colorbarLocation.empty())
1050 os <<
",\"colorbarLocation\":\"" << ax.colorbarLocation <<
"\"";
1052 os <<
",\"yyEnabled\":true";
1053 if (!ax.ylabel2.empty())
1054 os <<
",\"ylabel2\":\"" << jsonEscapeFig(ax.ylabel2) <<
"\"";
1055 if (!ax.ylim2Json.empty())
1056 os <<
",\"ylim2\":" << ax.ylim2Json;
1057 if (ax.yscale2 !=
"linear")
1058 os <<
",\"yscale2\":\"" << ax.yscale2 <<
"\"";
1060 if (!ax.viewJson.empty())
1061 os <<
",\"view\":" << ax.viewJson;
1062 if (!ax.zlabel.empty())
1063 os <<
",\"zlabel\":\"" << jsonEscapeFig(ax.zlabel) <<
"\"";
1064 if (!ax.zlimJson.empty())
1065 os <<
",\"zlim\":" << ax.zlimJson;
1066 if (!ax.lightingMode.empty())
1067 os <<
",\"lighting\":\"" << ax.lightingMode <<
"\"";
1068 if (!ax.materialPreset.empty())
1069 os <<
",\"material\":\"" << ax.materialPreset <<
"\"";
1070 if (!ax.camlightPos.empty())
1071 os <<
",\"camlight\":\"" << ax.camlightPos <<
"\"";
1072 if (!ax.rotate3dMode.empty())
1073 os <<
",\"rotate3d\":\"" << ax.rotate3dMode <<
"\"";
1074 if (!ax.pan3dMode.empty())
1075 os <<
",\"pan3d\":\"" << ax.pan3dMode <<
"\"";
1076 if (!ax.zoom3dMode.empty())
1077 os <<
",\"zoom3d\":\"" << ax.zoom3dMode <<
"\"";
1081 std::string line = os.str() +
"\n";
1089 void closeFigure(
int id)
1091 invalidateHandles(
id, -1,
true,
true);
1093 if (currentFigure_ ==
id) {
1094 if (!figures_.empty())
1095 currentFigure_ = figures_.rbegin()->first;
1105 std::string marker =
"__FIGURE_CLOSE__:" + std::to_string(
id) +
"\n";
1106 if (outputFunc_) outputFunc_(marker);
else std::cout << marker;
1109 void closeCurrent() { closeFigure(currentFigure_); }
1111 void closeCurrentNotify()
1113 int id = currentFigure_;
1115 std::string marker =
"__FIGURE_CLOSE__:" + std::to_string(
id) +
"\n";
1116 if (outputFunc_) outputFunc_(marker);
else std::cout << marker;
1121 for (
auto &[hid, rec] : handleRegistry_)
1127 void closeAllNotify()
1130 std::string marker =
"__FIGURE_CLOSE_ALL__\n";
1131 if (outputFunc_) outputFunc_(marker);
else std::cout << marker;
1134 const std::map<int, FigureState> &figures()
const {
return figures_; }
1144 bool holdOn =
false;
1145 size_t datasetCount = 0;
1151 std::vector<std::pair<int, std::vector<AxesCheckpoint>>> figures;
1188 double x0,
double x1,
int width,
int algo)
const
1191 auto it = figures_.find(figId);
1192 if (it == figures_.end())
return out;
1193 const auto &fig = it->second;
1194 if (axIdx < 0 || axIdx >=
static_cast<int>(fig.axes.size()))
return out;
1195 const auto &ax = fig.axes[axIdx];
1196 if (dsIdx < 0 || dsIdx >=
static_cast<int>(ax.datasets.size()))
return out;
1197 const auto &ds = ax.datasets[dsIdx];
1198 if (ds.xRaw.empty() || ds.xRaw.size() != ds.yRaw.size())
return out;
1201 if (ds.seriesPyramid.empty() && ds.xRaw.size() > 8000)
1202 ds.seriesPyramid = buildPyramid(ds.xRaw.data(), ds.yRaw.data(), ds.xRaw.size());
1203 DecimAlgo a = (algo == 1) ? DecimAlgo::LTTB
1204 : (algo == 2) ? DecimAlgo::None
1205 : (algo == 3) ? DecimAlgo::M2 : DecimAlgo::M4;
1206 return decimateLOD(ds.xRaw.data(), ds.yRaw.data(), ds.xRaw.size(),
1207 ds.seriesPyramid, x0, x1, width, a);
1210 bool getFigureDisplayTile(
int figId,
int axIdx,
int dsIdx,
1211 double srcR0,
double srcC0,
1212 double srcH,
double srcW,
1213 int displayH,
int displayW,
1214 bool xLog,
bool yLog,
1217 if (!out || displayH <= 0 || displayW <= 0)
return false;
1219 auto figIt = figures_.find(figId);
1220 if (figIt == figures_.end())
return false;
1221 const auto &fig = figIt->second;
1222 if (axIdx < 0 || axIdx >=
static_cast<int>(fig.axes.size()))
return false;
1223 const auto &axState = fig.axes[axIdx];
1224 if (dsIdx < 0 || dsIdx >=
static_cast<int>(axState.datasets.size()))
return false;
1225 const auto &ds = axState.datasets[dsIdx];
1226 if (ds.zQuantized.empty() || ds.type !=
"imagesc")
return false;
1228 const size_t fullRows = ds.originalRows;
1229 const size_t fullCols = ds.originalCols;
1230 if (fullRows == 0 || fullCols == 0)
return false;
1231 if (ds.zQuantized.size() < fullRows * fullCols)
return false;
1234 if (srcR0 < 0) { srcH += srcR0; srcR0 = 0; }
1235 if (srcC0 < 0) { srcW += srcC0; srcC0 = 0; }
1236 const double srcR1 = std::min(
static_cast<double>(fullRows), srcR0 + srcH);
1237 const double srcC1 = std::min(
static_cast<double>(fullCols), srcC0 + srcW);
1238 if (srcR1 <= srcR0 || srcC1 <= srcC0)
return false;
1241 if (yLog && srcR0 <= 0)
return false;
1242 if (xLog && srcC0 <= 0)
return false;
1249 const double rStep0 = (srcR1 - srcR0) / displayH;
1250 const double cStep0 = (srcC1 - srcC0) / displayW;
1251 const double minStep = std::min(rStep0, cStep0);
1253 if (minStep > 1.5) {
1254 level =
static_cast<int>(std::floor(std::log2(minStep)));
1255 if (level < 0) level = 0;
1256 if (level > 10) level = 10;
1261 const uint8_t *lvlData;
1262 size_t lvlRows, lvlCols;
1264 lvlData = ds.zQuantized.data();
1267 }
else if (level <=
static_cast<int>(ds.lodLevels.size())) {
1268 lvlData = ds.lodLevels[level - 1].data();
1269 lvlRows = ds.lodDims[level - 1].first;
1270 lvlCols = ds.lodDims[level - 1].second;
1273 lvlData = ds.zQuantized.data();
1280 const double scaleFactor =
static_cast<double>(1 << level);
1281 const double lvlR0 = srcR0 / scaleFactor;
1282 const double lvlC0 = srcC0 / scaleFactor;
1283 const double lvlR1 = srcR1 / scaleFactor;
1284 const double lvlC1 = srcC1 / scaleFactor;
1285 const double rStep = (lvlR1 - lvlR0) / displayH;
1286 const double cStep = (lvlC1 - lvlC0) / displayW;
1287 const int rBlock = std::max(1,
static_cast<int>(std::ceil(rStep)));
1288 const int cBlock = std::max(1,
static_cast<int>(std::ceil(cStep)));
1291 const double yLogRatio = yLog ? std::log(lvlR1 / lvlR0) : 0.0;
1292 const double xLogRatio = xLog ? std::log(lvlC1 / lvlC0) : 0.0;
1294 for (
int orow = 0; orow < displayH; ++orow) {
1295 const double v = (orow + 0.5) / displayH;
1296 const double rCenter = yLog
1297 ? lvlR0 * std::exp(v * yLogRatio)
1298 : lvlR0 + v * (lvlR1 - lvlR0);
1299 const int rA = std::max(0,
static_cast<int>(rCenter) - rBlock / 2);
1300 const int rB = std::min(
static_cast<int>(lvlRows), rA + rBlock);
1301 for (
int ocol = 0; ocol < displayW; ++ocol) {
1302 const double u = (ocol + 0.5) / displayW;
1303 const double cCenter = xLog
1304 ? lvlC0 * std::exp(u * xLogRatio)
1305 : lvlC0 + u * (lvlC1 - lvlC0);
1306 const int cA = std::max(0,
static_cast<int>(cCenter) - cBlock / 2);
1307 const int cB = std::min(
static_cast<int>(lvlCols), cA + cBlock);
1309 for (
int cc = cA; cc < cB; ++cc) {
1310 for (
int rr = rA; rr < rB; ++rr) {
1311 const uint8_t q = lvlData[cc * lvlRows + rr];
1312 if (q != 255) {
sum += q; ++n; }
1315 out[orow * displayW + ocol] = (n > 0)
1316 ?
static_cast<uint8_t
>((sum + n / 2) / n)
1333 while (
static_cast<int>(ds.lodLevels.size()) < targetLevel) {
1334 size_t pRows, pCols;
1335 const uint8_t *parent;
1336 if (ds.lodLevels.empty()) {
1337 parent = ds.zQuantized.data();
1338 pRows = ds.originalRows;
1339 pCols = ds.originalCols;
1341 const auto &dims = ds.lodDims.back();
1343 pCols = dims.second;
1344 parent = ds.lodLevels.back().data();
1346 const size_t cRows = (pRows + 1) / 2;
1347 const size_t cCols = (pCols + 1) / 2;
1348 if (cRows < 32 || cCols < 32)
break;
1350 std::vector<uint8_t> child(cRows * cCols);
1351 for (
size_t cc = 0; cc < cCols; ++cc) {
1352 const size_t pc0 = 2 * cc;
1353 const size_t pc1 = std::min(pCols, pc0 + 2);
1354 for (
size_t rr = 0; rr < cRows; ++rr) {
1355 const size_t pr0 = 2 * rr;
1356 const size_t pr1 = std::min(pRows, pr0 + 2);
1358 for (
size_t pc = pc0; pc < pc1; ++pc) {
1359 for (
size_t pr = pr0; pr < pr1; ++pr) {
1360 const uint8_t q = parent[pc * pRows + pr];
1361 if (q != 255) { sum += q; ++n; }
1364 child[cc * cRows + rr] = (n > 0)
1365 ?
static_cast<uint8_t
>((sum + n / 2) / n)
1369 ds.lodLevels.push_back(std::move(child));
1370 ds.lodDims.emplace_back(cRows, cCols);
1382 int r0,
int c0,
int h,
int w,
int lod,
1383 std::vector<uint8_t> &out,
1384 size_t &outRows,
size_t &outCols)
const
1390 auto figIt = figures_.find(figId);
1391 if (figIt == figures_.end())
return false;
1392 const auto &fig = figIt->second;
1393 if (axIdx < 0 || axIdx >=
static_cast<int>(fig.axes.size()))
return false;
1394 const auto &axState = fig.axes[axIdx];
1395 if (dsIdx < 0 || dsIdx >=
static_cast<int>(axState.datasets.size()))
return false;
1396 const auto &ds = axState.datasets[dsIdx];
1397 if (ds.zQuantized.empty() || ds.type !=
"imagesc")
return false;
1399 const size_t fullRows = ds.originalRows;
1400 const size_t fullCols = ds.originalCols;
1401 if (fullRows == 0 || fullCols == 0)
return false;
1402 if (ds.zQuantized.size() < fullRows * fullCols)
return false;
1404 if (lod < 1) lod = 1;
1407 if (h < 0 || w < 0)
return false;
1409 const size_t rEnd = std::min(fullRows,
static_cast<size_t>(r0) +
static_cast<size_t>(h));
1410 const size_t cEnd = std::min(fullCols,
static_cast<size_t>(c0) +
static_cast<size_t>(w));
1411 if (
static_cast<size_t>(r0) >= fullRows ||
static_cast<size_t>(c0) >= fullCols
1412 || rEnd <=
static_cast<size_t>(r0) || cEnd <=
static_cast<size_t>(c0)) {
1416 const size_t srcH = rEnd -
static_cast<size_t>(r0);
1417 const size_t srcW = cEnd -
static_cast<size_t>(c0);
1418 const size_t L =
static_cast<size_t>(lod);
1419 const size_t oH = (srcH + L - 1) / L;
1420 const size_t oW = (srcW + L - 1) / L;
1422 out.resize(oH * oW);
1426 for (
size_t orow = 0; orow < oH; ++orow) {
1427 const size_t rA =
static_cast<size_t>(r0) + orow * L;
1428 const size_t rB = std::min(rEnd, rA + L);
1429 for (
size_t ocol = 0; ocol < oW; ++ocol) {
1430 const size_t cA =
static_cast<size_t>(c0) + ocol * L;
1431 const size_t cB = std::min(cEnd, cA + L);
1434 for (
size_t cc = cA; cc < cB; ++cc) {
1435 for (
size_t rr = rA; rr < rB; ++rr) {
1436 const uint8_t q = ds.zQuantized[cc * fullRows + rr];
1443 out[orow * oW + ocol] = (n > 0)
1444 ?
static_cast<uint8_t
>((sum + n / 2) / n)
1455 std::map<int, FigureState> figures_;
1456 int currentFigure_ = 1;
1457 OutputFunc outputFunc_;
1458 int nextHandleId_ = 1;
1459 std::unordered_map<int, HandleRecord> handleRegistry_;
1460 size_t chartCallBase_ = 0;