@@ -750,14 +750,68 @@ 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// -----------------------------------------------------------------------------
756810
757811constexpr float TICK_FILL_X = 0 .8f ;
758812constexpr float TICK_FILL_Y = 1 .0f ;
759813
760- void Locator_Default (ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void * formatter_data, double * offset, int * log10_multiplier ) {
814+ void Locator_Default (ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void * formatter_data) {
761815 if (range.Min == range.Max )
762816 return ;
763817 const int nMinor = 10 ;
@@ -768,29 +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 ;
771- bool delta_mode = false ;
772- if ((range.Max > 0 ) == (range.Min > 0 )) {
773- double min_abs = ImMin (ImAbs (range.Min ),ImAbs (range.Max ));
774- *log10_multiplier = (int )IM_ROUND (ImLog10 ((range.Max - range.Min )));
775- if (*log10_multiplier < 0 ) {
776- *log10_multiplier = *log10_multiplier - 1 ;
777- }
778- *log10_multiplier = ImSign (static_cast <double >(*log10_multiplier)) * IM_TRUNC (ImAbs (static_cast <double >(*log10_multiplier)/3 .)) * 3 ;
779-
780- delta_mode = !(*log10_multiplier==0 );
781-
782- if (delta_mode) {
783- double ten_base = ImPow (10 .,-static_cast <double >(*log10_multiplier) + 2 * ((*log10_multiplier < 0 ) ? -1 : 1 ));
784825
785- *offset = (range.Max > 0 ) ? floor (graphmin*ten_base) / ten_base : ceil (graphmax*ten_base) / ten_base;
786-
787- } else {
788- *offset = 0 ;
789- }
790-
791- }
792826 const int idx0 = ticker.TickCount (); // ticker may have user custom ticks
793827 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+ }
794837 for (double major = graphmin; major < graphmax + 0.5 * interval; major += interval) {
795838 // is this zero? combat zero formatting issues
796839 if (major-interval < 0 && major+interval > 0 )
@@ -800,18 +843,12 @@ void Locator_Default(ImPlotTicker& ticker, const ImPlotRange& range, float pixel
800843 first_major_idx = ticker.TickCount ();
801844 first_major_set = true ;
802845 }
803- char label[16 ];
804- sprintf (label, (char *) formatter_data, (major - (delta_mode ? *offset : 0 .))* (delta_mode ? pow (10 ,-*log10_multiplier) : 1 ));
805- total_size += ticker.AddTick (major, true , 0 , true , label).LabelSize ;
806- // total_size += ticker.AddTick(major, true, 0, true, formatter, formatter_data).LabelSize;
846+ total_size += ticker.AddTick (major, true , 0 , true , formatter, formatter_data).LabelSize ;
807847 }
808848 for (int i = 1 ; i < nMinor; ++i) {
809849 double minor = major + i * interval / nMinor;
810850 if (range.Contains (minor)) {
811- char label[16 ];
812- sprintf (label, (char *) formatter_data, (minor - (delta_mode ? *offset : 0 .))* (delta_mode ? pow (10 ,-*log10_multiplier) : 1 ));
813- total_size += ticker.AddTick (minor, false , 0 , true , label).LabelSize ;
814- // total_size += ticker.AddTick(minor, false, 0, true, formatter, formatter_data).LabelSize;
851+ total_size += ticker.AddTick (minor, false , 0 , true , formatter, formatter_data).LabelSize ;
815852 }
816853 }
817854 }
@@ -822,6 +859,10 @@ void Locator_Default(ImPlotTicker& ticker, const ImPlotRange& range, float pixel
822859 for (int i = first_major_idx+1 ; i < ticker.TickCount (); i += 2 )
823860 ticker.Ticks [i].ShowLabel = false ;
824861 }
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+ }
825866}
826867
827868bool CalcLogarithmicExponents (const ImPlotRange& range, float pix, bool vertical, int & exp_min, int & exp_max, int & exp_step) {
@@ -864,7 +905,7 @@ void AddTicksLogarithmic(const ImPlotRange& range, int exp_min, int exp_max, int
864905 }
865906}
866907
867- void Locator_Log10 (ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void * formatter_data, double * offset, int * log10_multiplier ) {
908+ void Locator_Log10 (ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void * formatter_data) {
868909 int exp_min, exp_max, exp_step;
869910 if (CalcLogarithmicExponents (range, pixels, vertical, exp_min, exp_max, exp_step))
870911 AddTicksLogarithmic (range, exp_min, exp_max, exp_step, ticker, formatter, formatter_data);
@@ -881,7 +922,7 @@ float CalcSymLogPixel(double plt, const ImPlotRange& range, float pixels) {
881922 return (float )(0 + scaleToPixels * (plt - range.Min ));
882923}
883924
884- void Locator_SymLog (ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void * formatter_data, double * offset, int * log10_multiplier ) {
925+ void Locator_SymLog (ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void * formatter_data) {
885926 if (range.Min >= -1 && range.Max <= 1 ) {
886927 Locator_Default (ticker, range, pixels, vertical, formatter, formatter_data);
887928 }
@@ -1284,7 +1325,7 @@ inline ImPlotDateTimeSpec GetDateTimeFmt(const ImPlotDateTimeSpec* ctx, ImPlotTi
12841325 return fmt;
12851326}
12861327
1287- void Locator_Time (ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void * formatter_data, double * offset, int * log10_multiplier ) {
1328+ void Locator_Time (ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void * formatter_data) {
12881329 IM_ASSERT_USER_ERROR (vertical == false , " Cannot locate Time ticks on vertical axis!" );
12891330 (void )vertical;
12901331 // get units for level 0 and level 1 labels
@@ -1675,7 +1716,11 @@ void LabelAxisValue(const ImPlotAxis& axis, double value, char* buff, int size,
16751716 else {
16761717 if (round)
16771718 value = RoundAxisValue (axis, value);
1678- 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+ }
16791724 }
16801725}
16811726
@@ -2540,6 +2585,59 @@ bool BeginPlot(const char* title_id, const ImVec2& size, ImPlotFlags flags) {
25402585// SetupFinish
25412586// -----------------------------------------------------------------------------
25422587
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+
25432641void SetupFinish () {
25442642 IM_ASSERT_USER_ERROR (GImPlot != nullptr , " No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?" );
25452643 ImPlotContext& gp = *GImPlot;
@@ -2569,9 +2667,12 @@ void SetupFinish() {
25692667 else
25702668 axis.FormatterData = (void *)IMPLOT_LABEL_FORMAT ;
25712669 }
2670+
25722671 if (axis.Locator == nullptr ) {
25732672 axis.Locator = Locator_Default;
25742673 }
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" );
25752676 }
25762677
25772678 // setup nullptr orthogonal axes
@@ -2645,12 +2746,10 @@ void SetupFinish() {
26452746 const float plot_height = plot.CanvasRect .GetHeight () - pad_top - pad_bot;
26462747
26472748 // (2) get y tick labels (needed for left/right pad)
2648- double y_offset = 0 .;
2649- int y_log10_multiplier = 0 .;
26502749 for (int i = 0 ; i < IMPLOT_NUM_Y_AXES ; i++) {
26512750 ImPlotAxis& axis = plot.YAxis (i);
26522751 if (axis.WillRender () && axis.ShowDefaultTicks && plot_height > 0 ) {
2653- axis.Locator (axis.Ticker , axis.Range , plot_height, true , axis.Formatter , axis.FormatterData , &y_offset, &y_log10_multiplier );
2752+ axis.Locator (axis.Ticker , axis.Range , plot_height, true , axis.Formatter , axis.FormatterData );
26542753 }
26552754 }
26562755
@@ -2660,12 +2759,10 @@ void SetupFinish() {
26602759 const float plot_width = plot.CanvasRect .GetWidth () - pad_left - pad_right;
26612760
26622761 // (4) get x ticks
2663- double x_offset = 0 .;
2664- int x_log10_multiplier = 0 .;
26652762 for (int i = 0 ; i < IMPLOT_NUM_X_AXES ; i++) {
26662763 ImPlotAxis& axis = plot.XAxis (i);
26672764 if (axis.WillRender () && axis.ShowDefaultTicks && plot_width > 0 ) {
2668- axis.Locator (axis.Ticker , axis.Range , plot_width, false , axis.Formatter , axis.FormatterData , &x_offset, &x_log10_multiplier );
2765+ axis.Locator (axis.Ticker , axis.Range , plot_width, false , axis.Formatter , axis.FormatterData );
26692766 }
26702767 }
26712768
@@ -2785,28 +2882,12 @@ void SetupFinish() {
27852882 const ImPlotTicker& tkr = ax.Ticker ;
27862883 const bool opp = ax.IsOpposite ();
27872884 if (ax.HasLabel ()) {
2788- const char * base_label = plot.GetAxisLabel (ax);
2789- char label[36 ];
2790- if (x_offset==0 . && x_log10_multiplier==0 ) {
2791- strcpy (label, base_label);
2885+ char label[IMPLOT_LABEL_MAX_SIZE ];
2886+ if (ax.Formatter == Formatter_Offset_Plus_Delta && ImHasFlag (ax.Flags , ImPlotAxisFlags_CompactOffsetAndDelta)) {
2887+ AxisLabelWithOffsetAndDeltaDecorations (label, ax);
27922888 } else {
2793- strcpy (label, " (" );
2794- strcat (label, base_label);
2795- char label_offset_label[12 ];
2796- sprintf (label_offset_label, (char *)ax.FormatterData , ImAbs (x_offset));
2797- if (x_offset>0 ) {
2798- strcat (label," - " );
2799- } else if (x_offset < 0 ){
2800- strcat (label," + " );
2801- }
2802- char label_log10_label[12 ];
2803- sprintf (label_log10_label, " ) x 10^%d" , -x_log10_multiplier);
2804- if (x_offset!=0 ) {
2805- strcat (label, label_offset_label);
2806- }
2807- strcat (label, label_log10_label);
2889+ ImFormatString (label, IMPLOT_LABEL_MAX_SIZE , " %s" , plot.GetAxisLabel (ax));
28082890 }
2809-
28102891 const ImVec2 label_size = ImGui::CalcTextSize (label);
28112892 const float label_offset = (ax.HasTickLabels () ? tkr.MaxSize .y + gp.Style .LabelPadding .y : 0 .0f )
28122893 + (tkr.Levels - 1 ) * (txt_height + gp.Style .LabelPadding .y )
@@ -2827,6 +2908,20 @@ void SetupFinish() {
28272908 }
28282909 }
28292910 }
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+
28302925 }
28312926
28322927 // render y axis button, label, tick labels
@@ -2846,7 +2941,12 @@ void SetupFinish() {
28462941 const ImPlotTicker& tkr = ax.Ticker ;
28472942 const bool opp = ax.IsOpposite ();
28482943 if (ax.HasLabel ()) {
2849- 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+ }
28502950 const ImVec2 label_size = CalcTextSizeVertical (label);
28512951 const float label_offset = (ax.HasTickLabels () ? tkr.MaxSize .x + gp.Style .LabelPadding .x : 0 .0f )
28522952 + gp.Style .LabelPadding .x ;
@@ -2864,6 +2964,14 @@ void SetupFinish() {
28642964 }
28652965 }
28662966 }
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+ }
28672975 }
28682976
28692977
0 commit comments