@@ -750,6 +750,60 @@ bool ShowLegendEntries(ImPlotItemGroup& items, const ImRect& legend_bb, bool hov
750750 return hovered && !any_item_hovered;
751751}
752752
753+
754+
755+ // -----------------------------------------------------------------------------
756+ // Offset plus delta formatter
757+ // -----------------------------------------------------------------------------
758+ int FormatOffsetPlusDelta (double value, char * buff, int size, char * fmt, int decimal_shift, double offset) {
759+ return ImFormatString (buff, size, fmt, (value - offset) * ImPow (10 ., (double ) -decimal_shift));
760+ }
761+ bool UseOffset (ImPlotRange Range) {
762+ if ((Range.Max > 0 ) != (Range.Min > 0 )) {
763+ return false ;
764+ }
765+ int range_most_sig_place = (int ) floor (ImLog10 (Range.Max - Range.Min ));
766+ int extremum_most_sig_place = (int ) floor (ImLog10 (ImMax (ImAbs (Range.Max ),ImAbs (Range.Min ))));
767+ // present the ticks as a delta relative to an offset if the most significant digit of the
768+ // range is at least 3 digits to the right of the most significant digit of the extremum
769+ return range_most_sig_place - extremum_most_sig_place <= -3 ;
770+ }
771+
772+ int GetDecimalShift (ImPlotRange Range) {
773+ int decimal_shift;
774+ int n_axis_unit_thresh_log10 = 1 ;
775+ if (UseOffset (Range)) {
776+ // if the range is not between 0.01 and 10 user units, then scale the labels by a factor
777+ // 10^(3n) to ensure the range is between 0.01 and 10 label units. here, a user unit is a
778+ // change of 1 in the units employed by the user, and a label unit is a change of 1 in the
779+ // units used for the tick labeling
780+ decimal_shift = ((int )((ImLog10 (Range.Max - Range.Min ) - 1 )/3 )) * 3 ;
781+ } else {
782+ // if the abs of the most extreme value is not between 0.1 and 100 user units, then scale
783+ // the labels by a factor 10^(3n) to ensure the abs of the most extreme label value is
784+ // between 0.1 and 100 label units
785+ decimal_shift = ((int )((ImLog10 (ImMax (ImAbs (Range.Max ),ImAbs (Range.Min ))) - 2 )/3 )) * 3 ;
786+ }
787+ return decimal_shift;
788+ }
789+
790+ double GetOffset (ImPlotRange Range) {
791+ double offset;
792+ if (UseOffset (Range)) {
793+ int range_most_sig_place = (int ) floor (ImLog10 (Range.Max - Range.Min ));
794+ double minabs = (Range.Max > 0 ) ? Range.Min : Range.Max ;
795+ // how often should the offset change as the user pans across the axis? the two lines
796+ // below set the axis offset to the range min (positive values) or the range max
797+ // (negative values) truncated to 2 digits to the left of the range_most_sig_place.
798+ // the place value of this place is between 10 and 100 times the axis range
799+ float ten_base = pow (10 .,-range_most_sig_place - 2 );
800+ offset = (minabs > 0 ) ? floor (minabs*ten_base) / ten_base : ceil (minabs*ten_base)/ten_base;
801+ } else {
802+ offset = 0 .;
803+ }
804+ return offset;
805+ }
806+
753807// -----------------------------------------------------------------------------
754808// Locators
755809// -----------------------------------------------------------------------------
@@ -768,8 +822,18 @@ void Locator_Default(ImPlotTicker& ticker, const ImPlotRange& range, float pixel
768822 const double graphmax = ceil (range.Max / interval) * interval;
769823 bool first_major_set = false ;
770824 int first_major_idx = 0 ;
825+
771826 const int idx0 = ticker.TickCount (); // ticker may have user custom ticks
772827 ImVec2 total_size (0 ,0 );
828+ Formatter_Offset_Plus_Delta_Data fopdd;
829+ if (formatter == Formatter_Offset_Plus_Delta) {
830+ ticker.LabelDecimalShift = GetDecimalShift (range);
831+ ticker.LabelOffset = GetOffset (range);
832+ fopdd.decimal_shift = ticker.LabelDecimalShift ;
833+ fopdd.offset = ticker.LabelOffset ;
834+ fopdd.fmt = (char *) formatter_data;
835+ formatter_data = &fopdd;
836+ }
773837 for (double major = graphmin; major < graphmax + 0.5 * interval; major += interval) {
774838 // is this zero? combat zero formatting issues
775839 if (major-interval < 0 && major+interval > 0 )
@@ -795,6 +859,10 @@ void Locator_Default(ImPlotTicker& ticker, const ImPlotRange& range, float pixel
795859 for (int i = first_major_idx+1 ; i < ticker.TickCount (); i += 2 )
796860 ticker.Ticks [i].ShowLabel = false ;
797861 }
862+ if (formatter == Formatter_Offset_Plus_Delta) {
863+ // reset formatter_data so that it doesn't point to fopdd when fopdd goes out of scope
864+ formatter_data = fopdd.fmt ;
865+ }
798866}
799867
800868bool CalcLogarithmicExponents (const ImPlotRange& range, float pix, bool vertical, int & exp_min, int & exp_max, int & exp_step) {
@@ -1648,7 +1716,11 @@ void LabelAxisValue(const ImPlotAxis& axis, double value, char* buff, int size,
16481716 else {
16491717 if (round)
16501718 value = RoundAxisValue (axis, value);
1651- axis.Formatter (value, buff, size, axis.FormatterData );
1719+ if (axis.Formatter == Formatter_Offset_Plus_Delta) {
1720+ Formatter_Default (value, buff, size, axis.FormatterData );
1721+ } else {
1722+ axis.Formatter (value, buff, size, axis.FormatterData );
1723+ }
16521724 }
16531725}
16541726
@@ -2513,6 +2585,59 @@ bool BeginPlot(const char* title_id, const ImVec2& size, ImPlotFlags flags) {
25132585// SetupFinish
25142586// -----------------------------------------------------------------------------
25152587
2588+ void AxisLabelWithOffsetAndDeltaDecorations (char * label, ImPlotAxis ax) {
2589+ ImPlotPlot &plot = *GImPlot->CurrentPlot ;
2590+ const char * base_label = plot.GetAxisLabel (ax);
2591+ if (ax.Formatter == Formatter_Offset_Plus_Delta) {
2592+ char offset_label[IMPLOT_LABEL_MAX_SIZE ];
2593+ char decimal_shift_label[IMPLOT_LABEL_MAX_SIZE ];
2594+ if (ax.Ticker .LabelDecimalShift != 0 ) {
2595+ ImFormatString (decimal_shift_label, IMPLOT_LABEL_MAX_SIZE , " x 10^%d" , -ax.Ticker .LabelDecimalShift );
2596+ } else {
2597+ decimal_shift_label[0 ] = ' \0 ' ;
2598+ }
2599+ if (ax.Ticker .LabelOffset != 0 ) {
2600+ ImFormatString (offset_label, IMPLOT_LABEL_MAX_SIZE , " %c %g" ,
2601+ ax.Ticker .LabelOffset > 0 ? ' -' : ' +' ,
2602+ ImAbs (ax.Ticker .LabelOffset ));
2603+ } else {
2604+ offset_label[0 ] = ' \0 ' ;
2605+ }
2606+
2607+ bool both_on = (ax.Ticker .LabelDecimalShift != 0 ) && (ax.Ticker .LabelOffset != 0 );
2608+ ImFormatString (label, IMPLOT_LABEL_MAX_SIZE , " %s%s%s%s%s" ,
2609+ both_on ? " (" : " " ,
2610+ base_label,
2611+ offset_label,
2612+ both_on ? " )" : " " ,
2613+ decimal_shift_label);
2614+ } else {
2615+ ImFormatString (label, IMPLOT_LABEL_MAX_SIZE , " %s" , base_label);
2616+ }
2617+ }
2618+
2619+ void OffsetAndDeltaLabel (char * label, ImPlotAxis ax) {
2620+ ImPlotPlot &plot = *GImPlot->CurrentPlot ;
2621+ char offset_label[IMPLOT_LABEL_MAX_SIZE ];
2622+ char decimal_shift_label[IMPLOT_LABEL_MAX_SIZE ];
2623+ if (ax.Ticker .LabelDecimalShift != 0 ) {
2624+ ImFormatString (decimal_shift_label, IMPLOT_LABEL_MAX_SIZE , " x10^%d" , ax.Ticker .LabelDecimalShift );
2625+ } else {
2626+ decimal_shift_label[0 ] = ' \0 ' ;
2627+ }
2628+ if (ax.Ticker .LabelOffset != 0 ) {
2629+ ImFormatString (offset_label, IMPLOT_LABEL_MAX_SIZE , " %c %g" ,
2630+ ax.Ticker .LabelOffset > 0 ? ' +' : ' -' ,
2631+ ImAbs (ax.Ticker .LabelOffset ));
2632+ } else {
2633+ offset_label[0 ] = ' \0 ' ;
2634+ }
2635+ ImFormatString (label, IMPLOT_LABEL_MAX_SIZE , " %s%s%s" ,
2636+ decimal_shift_label,
2637+ (ax.Ticker .LabelDecimalShift != 0 ) && (ax.Ticker .LabelOffset != 0 ) ? " " : " " ,
2638+ offset_label);
2639+ }
2640+
25162641void SetupFinish () {
25172642 IM_ASSERT_USER_ERROR (GImPlot != nullptr , " No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?" );
25182643 ImPlotContext& gp = *GImPlot;
@@ -2542,9 +2667,12 @@ void SetupFinish() {
25422667 else
25432668 axis.FormatterData = (void *)IMPLOT_LABEL_FORMAT ;
25442669 }
2670+
25452671 if (axis.Locator == nullptr ) {
25462672 axis.Locator = Locator_Default;
25472673 }
2674+
2675+ IM_ASSERT_USER_ERROR ((axis.Formatter != Formatter_Offset_Plus_Delta) || (axis.Locator == Locator_Default), " The Offset_Plus_Delta tick formatter can only be used with the default tick locator" );
25482676 }
25492677
25502678 // setup nullptr orthogonal axes
@@ -2754,7 +2882,12 @@ void SetupFinish() {
27542882 const ImPlotTicker& tkr = ax.Ticker ;
27552883 const bool opp = ax.IsOpposite ();
27562884 if (ax.HasLabel ()) {
2757- const char * label = plot.GetAxisLabel (ax);
2885+ char label[IMPLOT_LABEL_MAX_SIZE ];
2886+ if (ax.Formatter == Formatter_Offset_Plus_Delta && ImHasFlag (ax.Flags , ImPlotAxisFlags_CompactOffsetAndDelta)) {
2887+ AxisLabelWithOffsetAndDeltaDecorations (label, ax);
2888+ } else {
2889+ ImFormatString (label, IMPLOT_LABEL_MAX_SIZE , " %s" , plot.GetAxisLabel (ax));
2890+ }
27582891 const ImVec2 label_size = ImGui::CalcTextSize (label);
27592892 const float label_offset = (ax.HasTickLabels () ? tkr.MaxSize .y + gp.Style .LabelPadding .y : 0 .0f )
27602893 + (tkr.Levels - 1 ) * (txt_height + gp.Style .LabelPadding .y )
@@ -2763,6 +2896,7 @@ void SetupFinish() {
27632896 opp ? ax.Datum1 - label_offset - label_size.y : ax.Datum1 + label_offset);
27642897 DrawList.AddText (label_pos, ax.ColorTxt , label);
27652898 }
2899+
27662900 if (ax.HasTickLabels ()) {
27672901 for (int j = 0 ; j < tkr.TickCount (); ++j) {
27682902 const ImPlotTick& tk = tkr.Ticks [j];
@@ -2774,6 +2908,20 @@ void SetupFinish() {
27742908 }
27752909 }
27762910 }
2911+
2912+ if (ax.Formatter == Formatter_Offset_Plus_Delta && !ImHasFlag (ax.Flags , ImPlotAxisFlags_CompactOffsetAndDelta)) {
2913+ char offset_delta_label[IMPLOT_LABEL_MAX_SIZE ];
2914+ OffsetAndDeltaLabel (offset_delta_label, ax);
2915+ const ImVec2 label_size = ImGui::CalcTextSize (offset_delta_label);
2916+ const float label_offset = (ax.HasTickLabels () ? tkr.MaxSize .y + gp.Style .LabelPadding .y : 0 .0f )
2917+ + (tkr.Levels - 1 ) * (txt_height + gp.Style .LabelPadding .y );
2918+ const ImVec2 label_pos (plot.PlotRect .Max .x - label_size.x ,
2919+ opp ? ax.Datum1 - label_offset - label_size.y : ax.Datum1 + label_offset);
2920+ DrawList.AddText (label_pos, ax.ColorTxt , offset_delta_label);
2921+ }
2922+ //
2923+
2924+
27772925 }
27782926
27792927 // render y axis button, label, tick labels
@@ -2793,7 +2941,12 @@ void SetupFinish() {
27932941 const ImPlotTicker& tkr = ax.Ticker ;
27942942 const bool opp = ax.IsOpposite ();
27952943 if (ax.HasLabel ()) {
2796- const char * label = plot.GetAxisLabel (ax);
2944+ char label[IMPLOT_LABEL_MAX_SIZE ];
2945+ if (ax.Formatter == Formatter_Offset_Plus_Delta && ImHasFlag (ax.Flags , ImPlotAxisFlags_CompactOffsetAndDelta)) {
2946+ AxisLabelWithOffsetAndDeltaDecorations (label, ax);
2947+ } else {
2948+ ImFormatString (label, IMPLOT_LABEL_MAX_SIZE , " %s" , plot.GetAxisLabel (ax));
2949+ }
27972950 const ImVec2 label_size = CalcTextSizeVertical (label);
27982951 const float label_offset = (ax.HasTickLabels () ? tkr.MaxSize .x + gp.Style .LabelPadding .x : 0 .0f )
27992952 + gp.Style .LabelPadding .x ;
@@ -2811,6 +2964,14 @@ void SetupFinish() {
28112964 }
28122965 }
28132966 }
2967+ if (ax.Formatter == Formatter_Offset_Plus_Delta && !ImHasFlag (ax.Flags , ImPlotAxisFlags_CompactOffsetAndDelta)) {
2968+ char offset_delta_label[IMPLOT_LABEL_MAX_SIZE ];
2969+ OffsetAndDeltaLabel (offset_delta_label, ax);
2970+ const ImVec2 label_size = ImGui::CalcTextSize (offset_delta_label);
2971+ const ImVec2 label_pos (plot.PlotRect .Min .x ,
2972+ plot.PlotRect .Min .y - label_size.y );
2973+ DrawList.AddText (label_pos, ax.ColorTxt , offset_delta_label);
2974+ }
28142975 }
28152976
28162977
0 commit comments