/* * TickMarkDecorator.cc * * Created on: Dec 18, 2013 * Author: amdadev */ #include "TickMarkDecorator.hh" #include #include #include #include "log4cxx/logger.h" #include "plplot/plplot.h" #include "boost/exception/all.hpp" #include "PlotLogger.hh" #include "TimeAxis.hh" #include "Page.hh" #include "Time/TimePlot.hh" #include "TickPlot.hh" using boost::tuples::get; namespace plot { log4cxx::LoggerPtr TickMarkDecorator::_logger( log4cxx::Logger::getLogger("AMDA-Kernel.Plot.Time.TickMarkDecorator")); TickMarkDecorator::TickMarkDecorator (bool isStandalone, PanelPlotOutput* decoratorPlot) : TimeAxisDecorator(), _seriesInfoList(), _valuesFormat("% -4.4G"), _maxValueLen(0), _isStandalone(isStandalone), _decoratorPlot(decoratorPlot) { _pTimeInfo = TimeInfoPtr(new TimeInfo()); } TickMarkDecorator::TickMarkDecorator(const TickMarkDecorator& ref_) : TimeAxisDecorator(ref_), _seriesInfoList( ref_._seriesInfoList), _valuesFormat(ref_._valuesFormat), _maxValueLen( ref_._maxValueLen), _isStandalone(ref_._isStandalone), _decoratorPlot( ref_._decoratorPlot) { _pTimeInfo = TimeInfoPtr(new TimeInfo(*(ref_._pTimeInfo))); } TickMarkDecorator& TickMarkDecorator::operator=(const TickMarkDecorator& ref_) { if (_pTimeInfo == nullptr) { _pTimeInfo = boost::shared_ptr( new TimeInfo(*(ref_._pTimeInfo))); } else { _pTimeInfo->_timeFormat = ref_._pTimeInfo->_timeFormat; _pTimeInfo->_timeTitle = ref_._pTimeInfo->_timeTitle; _pTimeInfo->_min = ref_._pTimeInfo->_min; _pTimeInfo->_maxLen = ref_._pTimeInfo->_maxLen; } _seriesInfoList = ref_._seriesInfoList; _valuesFormat = ref_._valuesFormat; _maxValueLen = ref_._maxValueLen; _isStandalone = ref_._isStandalone; _decoratorPlot = ref_._decoratorPlot; return *this; } TickMarkDecorator::~TickMarkDecorator() { } void TickMarkDecorator::updatePlotArea(PanelPlotOutput* pplot_, Axis* axis_, Bounds& bounds_) { LOG4CXX_DEBUG(_logger, "TickMarkDecorator Updating plot area : "<getMargin(); // calculate char size for labels: Font legendFont(pAxis->_legend._font); Font panelFont(pplot_->_panel->_font); if (legendFont._size == 0) { legendFont = panelFont; } CharSize legendSize = pplot_->getCharacterSize(getPlFontScaleFactor(legendFont)); double tickmarkSpace = 0; if (pAxis->_visible) { // process room for label tickmarks : double lineHeight = legendSize.second * 2; // take into account first space between time label tickmarkSpace += lineHeight * 1.5; // reserve space for all series labels : tickmarkSpace += (lineHeight * _seriesInfoList.size()); } else { LOG4CXX_DEBUG(_logger, " Axis not visible nothing to do."); } // update y and height; x and width are left unchanged. // origin of viewport is at left,bottom corner. // to take into account the tickmarks. bounds_._height -= tickmarkSpace; // specific case when axis labels are displayed at bottom of the graph. Needs to update y. if (pAxis->_position == PlotCommon::POS_BOTTOM) { bounds_._y += tickmarkSpace; } // needs to reserve enough space for start date above CharSize startDateSize = pplot_->getCharacterSize( getPlFontScaleFactor(pplot_->_panel->_font)); bounds_._height -= startDateSize.second * 3; bounds_._y += startDateSize.second * 3; // take margins into account : // compute what we need for labels at left side : // reserve same space amount in both side (since text are centered on ticks, only half length is taken into account). double minspaceleft = std::max((const int) (_pTimeInfo->_maxLen + 1), (const int) _maxValueLen + 1); if (legendFont._size > panelFont._size) { minspaceleft *= legendSize.first; } else { CharSize panelSize = pplot_->getCharacterSize(getPlFontScaleFactor(panelFont)); minspaceleft *= panelSize.first; } minspaceleft *= 0.5; double minspaceright = minspaceleft; // we also need to reserve enough space for start date information // (only in width, because height is already taken into account in PlotPanelOutput). if (pAxis->_reverse) { minspaceright = std::max(minspaceright, computeStartDateWidth(pplot_, pAxis)); } else { minspaceleft = std::max(minspaceleft, computeStartDateWidth(pplot_, pAxis)); } // LOG4CXX_DEBUG(_logger,"min space="<(pAxis_->getRange().getMin()); tm * lTimeTm = gmtime(&lTime); char lTimeChr[80]; // Format date. size_t datelen = strftime(lTimeChr, 80, getPlStartTimeFormat(pAxis_->_timeFormat, pAxis_->getRange().getMin(), pAxis_->getRange().getMax()).c_str(), lTimeTm); CharSize fontSize = pplot_->getCharacterSize( getPlFontScaleFactor(pplot_->_panel->_font)); return fontSize.first * datelen; } void TickMarkDecorator::configure(PanelPlotOutput* pplot_, Axis* axis_, double start_, double stop_, std::map *pParameterValues) { LOG4CXX_DEBUG(_logger, "TickMarkDecorator::configure"); _pParameterValues = pParameterValues; // retrieve time axis TimeAxis* pTimeAxis = (TimeAxis*) axis_; // configure x axis range pTimeAxis->setRange(start_, stop_); // show xAxis or not pTimeAxis->setOnlyTickmarks(_isStandalone); setPlFormat( getPlTimeFormat(pTimeAxis->_timeFormat, pTimeAxis->getRange().getMin(), pTimeAxis->getRange().getMax(), getMajorTickNumber(pTimeAxis, pTimeAxis->getRange().getMin(), pTimeAxis->getRange().getMax()))); // install label generator installLabelGenerator(pplot_, pTimeAxis); } void TickMarkDecorator::draw(PanelPlotOutput* pplot_, Axis* axis_, std::shared_ptr pls_) { // draw a virtual axis for each entry // take into account plot orientation TimeAxis* pAxis = (TimeAxis*) axis_; // Get plot area viewport. Bounds vpBounds; PLFLT lXMin, lXMax, lYMin, lYMax; pls_->gvpd(lXMin, lXMax, lYMin, lYMax); vpBounds._x = lXMin; vpBounds._y = lYMin; vpBounds._width = lXMax - lXMin; vpBounds._height = lYMax - lYMin; // get windows Bounds winBounds; PLFLT xwmin, xwmax, ywmin, ywmax; pls_->gvpw(xwmin, xwmax, ywmin, ywmax); winBounds._x = xwmin; winBounds._y = ywmin; winBounds._width = xwmax - xwmin; winBounds._height = ywmax - ywmin; LOG4CXX_DEBUG(_logger, " viewport :"<getRange().getMin(), pAxis->getRange().getMax()); int nxsub = getMinorTickNumber(pAxis, pAxis->getRange().getMin(), pAxis->getRange().getMax(), xtick); // set color for drawing axis : Color lInitialColor = changeColor(pls_, pAxis->_color, pplot_->_panel->_page->_mode); // Set tick (major and minor) length factor PLFLT lTickLenFact = PanelPlotOutput::DEFAULT_TICK_LENGTH_FACTOR; if (!isnan(pAxis->_tick._lengthFactor)) { lTickLenFact = pAxis->_tick._lengthFactor; } // 0 => keep tick height in millimeters. pls_->smaj(0, lTickLenFact); pls_->smin(0, lTickLenFact); // Set font to draw axes. std::vector panelStyles; Font panelFont(pplot_->_panel->_font); std::vector legendStyles(pAxis->_legend._style); Font legendFont(pAxis->_legend._font); if (legendFont._size == 0) { // font has not been defined for that axis, used default panel font : legendFont = panelFont; legendStyles = panelStyles; } pls_->schr(getPlFontDef(legendFont), getPlFontScaleFactor(legendFont)); pls_->sfont(getPlFontFamily(legendFont), getPlFontStyle(legendStyles), getPlFontWeight(legendStyles)); // viewport translation only along y direction: we translate about n charHeight. So, gets panel character height. CharSize legendCharSize(pplot_->getCharacterSize(getPlFontScaleFactor(legendFont))); CharSize panelCharSize(pplot_->getCharacterSize(getPlFontScaleFactor(panelFont))); // delta is negative when axis is at bottom (which is the default case). positive otherwise. // (viewport origin is in the left bottom corner of the page). double yDelta = -legendCharSize.second * 2; std::string options = ""; int start; int end; int dir; if (pAxis->_position == PlotCommon::POS_TOP) { options = "mxo"; // options means : m=display above axe, x=do not plot ticks, o=user label function provided. yDelta = -yDelta; // if axis is displayed on top of graph we have to use an inverse translation delta. // reverse direction : start = _seriesInfoList.size() - 1; end = -1; dir = -1; } else { options = "nxo";// options means : n=display below axe, x=do not plot ticks, o=user label function provided. start = 0; end = _seriesInfoList.size(); dir = 1; } LOG4CXX_DEBUG(_logger, " viewport translation:"<_tick._position == Tick::TickPosition::OUTWARDS) { // set room for tick, tickmarks (graduation) and extra space between tickmarks (graduation) and data time. yDelta = getVerticalTickLength(pAxis, panelCharSize.second) + yDelta + (0.5 * yInc); } else { // for the first serie, we add an extra space (half of yInc) to add a sepration with time labels yDelta = yDelta + (0.5 * yInc); } for (int i = start; i != end; i += dir) { LOG4CXX_TRACE(_logger, " virtual axis ["<slabelfunc(generateYLabel, _seriesInfoList[i].get()); // translate viewport : pls_->vpor(vpBounds._x, vpBounds._x + vpBounds._width, vpBounds._y + yDelta, vpBounds._y + vpBounds._height + yDelta); // sets windows for new viewport : pls_->wind(winBounds._x, winBounds._x + winBounds._width, winBounds._y, winBounds._y + winBounds._height); pls_->box(options.c_str(), xtick, nxsub, "", 0, 0); yDelta = yDelta + yInc; } // restore viewport. pls_->vpor(vpBounds._x, vpBounds._x + vpBounds._width, vpBounds._y, vpBounds._y + vpBounds._height); // restore window pls_->wind(winBounds._x, winBounds._x + winBounds._width, winBounds._y, winBounds._y + winBounds._height); // Restore initial color. restoreColor(pls_, lInitialColor, pplot_->_panel->_page->_mode); } /** * @brief Search for each tick time, the closest parameter time in the paramdata_ and store the corresponding value. * Selected parameter time is in range ]ticktime-step, ticktime+step[ where step is the space between two tick. * If no such time is found for a given tick, associated value is marked as NaN. * Note that this algorithm may retrieve same value twice if there is exactly one time between two ticks * and it is exactly at the middle. * @param seriesdata_ series data to fill * @param pAxis_ related axis * @param paramdata_ related parameter * @param serieIndex_ series index. * @return longest label to display for that series (in number of char). */ unsigned int TickMarkDecorator::fillSeriesData(SeriesData& seriesdata_, TimeAxis* pAxis_, ParameterData& paramdata_, AMDA::Common::ParameterIndexComponent serieIndex_) { LOG4CXX_DEBUG(_logger, " filling series data..."); // string used to compute formattedvalue max length char formattedValue [40]; unsigned int maxlen = 0; // time range : double start = pAxis_->getRange().getMin(); double stop = pAxis_->getRange().getMax(); double step = getMajorTickSpace(pAxis_, start, stop); LOG4CXX_TRACE(_logger, " for all ticks between " << start << " and " << stop << " spaced by " << step); int index = -1; int size = paramdata_.getSize(); for (double tickTime = start; tickTime <= stop; tickTime += step) { // search index closest value to that time : double* ptime = paramdata_.getTimes(); int i = index + 1; while (i < size && (*(ptime + i) < tickTime)) { i++; } double cur = (i >= size) ? -1 : *(ptime + i); double prev = (i == 0) ? -1 : *(ptime + i - 1); if (prev != -1 && prev <= (tickTime - step)) { prev = -1; } if (cur != -1 && cur >= (tickTime + step)) { cur = -1; } if (prev == -1) { // only cur can be eligible if (cur == -1) { index = -1; // no suitable value } else { index = i; } } else if (cur == -1) { // only prev can be eligible and it is not -1 due to previous test index = i - 1; } else { // both are eligible, choose closest one : if ((tickTime - prev) > (cur - tickTime)) { index = i; } else { index = i - 1; } } // time found, retrieve related value or put a nan one: if (index == -1) { LOG4CXX_TRACE(_logger, "updating series data at time:"<_legend._text); // install time format _pTimeInfo->_timeFormat = getPlFormat(); _pTimeInfo->_timeTitle = timeAxis_->_legend._text; _pTimeInfo->_min = timeAxis_->getRange().getMin(); unsigned int maxNameLen = _pTimeInfo->_timeTitle.size(); // init maximum value len with formatted time : if (TimePlot::qsasconfig == NULL) { configqsas(1. / 86400., 0., 0., 0x0, 1, 1970, 0, 1, 0, 0, 0., &TimePlot::qsasconfig); } char formattedStr [40]; double v = 200800000000000; _maxValueLen = strfqsas(formattedStr, 40, _pTimeInfo->_timeFormat.c_str(), v, TimePlot::qsasconfig); // get decorator parameter value //_decoratorPlot->getDataFromServer(); // build all series names : _seriesInfoList.clear(); for (auto p : _decoratorPlot->_parameterAxesList) { const std::string name = p._originalParamId; LOG4CXX_DEBUG(_logger, " inspecting parameter : " << name); // list asked series : for (auto q : p.getYSerieIndexList(_pParameterValues)) { boost::shared_ptr pSerie = boost::shared_ptr(new SeriesInfo()); std::ostringstream osstr; osstr << name; // display index only if non scalar if(q.getDim1Index() != -1){ osstr << "[" << q.getDim1Index() ; if (q.getDim2Index() != -1) osstr << "," << q.getDim2Index(); osstr << "]"; } pSerie->_name = osstr.str(); if (pSerie->_name.size() > maxNameLen) { maxNameLen = pSerie->_name.size(); } pSerie->_min = timeAxis_->getRange().getMin(); pSerie->_valuesFormat = getValuesFormat(); // get serie at index : SeriesData data; _maxValueLen = std::max((const unsigned int) _maxValueLen, fillSeriesData(data, timeAxis_, (*_pParameterValues)[p.getYSeriePropertiesAt(q).getParamId()], q)); pSerie->_data = data; _seriesInfoList.push_back(pSerie); } } // normalize titles : _pTimeInfo->_maxLen = maxNameLen; for (auto p : _seriesInfoList) { p->_maxLen = maxNameLen; p->_maxValueLen = _maxValueLen; } // no legend for this axe since it is displayed at axis origin: timeAxis_->_legend._text = ""; //titles.str(); // install label generator for main axe (the one that is displayed) timeAxis_->_labelGenerator->_data = _pTimeInfo.get(); timeAxis_->_labelGenerator->_function = generateOrbitTimeLabel; } /***************************** EXTERNAL METHODS ********************************/ void generateYLabel(PLINT axis, PLFLT value, char *label, PLINT length, PLPointer data) { if (axis == PL_X_AXIS) { if (data == NULL) { std::cout << "WARNING !!!! NO DATA FOUND FOR generateYLabel FUNCTION !!!" << std::endl << std::endl; LOG4CXX_WARN(gLogger, "WARNING !!!! NO DATA FOUND FOR generateYLabel FUNCTION !!"); return; } SeriesInfo* pInfo = static_cast (data); // if value equals min, display _name string // otherwise, format value and display it if (value == pInfo->_min) { // do not display min value label // but display title : snprintf(label, length, "%*s", pInfo->_maxLen, pInfo->_name.c_str()); } else { // display formatted value std::ostringstream labels; // loops over each series and display value at this time auto q = pInfo->_data.find((double) value); if (q != pInfo->_data.end()) { // we found it ! labels << boost::format(pInfo->_valuesFormat) % q->second; } else { // not found just display Nan indicator labels << "NaN" << std::endl; } snprintf(label, length, "%s", labels.str().c_str()); } } } void generateOrbitTimeLabel(PLINT axis, PLFLT value, char *label, PLINT length, PLPointer data) { if (axis == PL_X_AXIS) { if (data == NULL) { std::cout << "WARNING !!!! NO DATA FOUND FOR generateOrbitTimeLabel FUNCTION !!!" << std::endl << std::endl; LOG4CXX_WARN(gLogger, "WARNING !!!! NO DATA FOUND FOR generateOrbitTimeLabel FUNCTION !!"); return; } TimeInfo* pInfo = static_cast (data); // get time format and min value // if value equals min, display an empty string // otherwise, format time and display formatted time if (value == pInfo->_min) { // do not display min value label // but display title : snprintf(label, length, "%*s", pInfo->_maxLen, pInfo->_timeTitle.c_str()); } else { // display formatted time std::string timeFormatstr = pInfo->_timeFormat.c_str(); char *timeFormat = new char[timeFormatstr.length() + 1]; strcpy(timeFormat, timeFormatstr.c_str()); if (TimePlot::qsasconfig == NULL) { configqsas(1. / 86400., 0., 0., 0x0, 1, 1970, 0, 1, 0, 0, 0., &TimePlot::qsasconfig); } char formattedTime[40]; strfqsas(formattedTime, 40, timeFormat, (double) value, TimePlot::qsasconfig); delete[] timeFormat; snprintf(label, length, "%s", formattedTime); } } } } /* namespace plot */