Skip to main content

dynamic - How to force Pane to always scroll to end of content?



Mathematica seems to know about the actual end of content position in a Pane if given large enough (but not too large$^1$) initial position value. Here, the vertical position is set to a very large number, but Mathematica sets pos to the appropriate actual value.


pos = {0, 10^100};
Framed@Pane["1\n2\n333333333333333333333\n4", ImageSize -> {100, 70},
ScrollPosition -> Dynamic[pos]]
Dynamic@pos

Mathematica graphics



How does Mathematica know that the correct vertical position is at 26. (on my machine), and, more importantly, how can I access this value directly? That is, given random content possibly including explicit or implicit linebreaks how can one find the appropriate scrollbar position value? Note that since there are implicit linebreaks (forced by Pane), one cannot rely on counting "\n" characters in the text. Needless to say, the solution should be robust enough to deal with different fonts, font sizes, styles and magnification automagically.


$\scriptsize{(^1 \text{Try it with pos = {0, 10^1000} and Mathematica fails to find the end.)}}$




The use case (warning, this might be a bit complicated)


I need a Pane that can be updated via a button: whenever the button is pressed, the text inside Pane is modified and I need to update the display frame to always scroll down (i.e. set Scrollbar position) to the end of content. Thus I need a 'listener' inside Pane that calculates the new end-of-content position whenever the content is changed from the outside.


The following minimal, though still convoluted example is what I came up with. This tracks the end-of-content correctly by simply counting "\n" characters in the dynamic text variable. Update text using the button to see how the Pane is always scrolled down to the end. Even if the slider (acting for a vertical scrollbar) is moved, any new update immediately scrolls down the content. A Refresh inside Pane (with specific TrackedSymbols option) acts as a listener to the external changes of text. I use a line height of 20 but lineHeight might be set manually for each platform.


pos = 10^3; (* set initial position value large enough *)
addCounter = 5; (* this counts the number of text-updates *)
text = "1\n2\n3\n4"; (* text variable that is updated directly *)
evalCounter = 0; (* count evaluations of the Refresh part of the code *)

lineHeight = 20; (* Magic number to specify line height *)
maxLength = 5; (* maximal line length *)

(* Update text with button, manually set position with slider *)
Row@{Button["Add text!", Block[{c, add},
c = ToString[addCounter++];
add = StringJoin@Table["\n" <> StringJoin@Table[c, {RandomInteger@{1, maxLength}}], {RandomInteger@{1, 2}}];
text = text <> add]],
Dynamic@Slider[Dynamic@pos, {0, (lineCounter)*lineHeight}]}


{Framed@Pane[Dynamic@text, ImageSize -> {100, 60},
ScrollPosition -> Dynamic[
{0,
(* Put code inside Refresh that is to be evaluated whenever pos changes directly. *)
Refresh[
If[evalCounter < 5, evalCounter++; Print[{evalCounter, pos}]];
pos, TrackedSymbols :> {text, pos}]
},
(* Put code inside second argument of Dynamic that is to be evaluated whenever pos changes indirectly. *)
(lineCounter = StringCount[text, "\n"];

pos = If[evalCounter < 5,
Last@# (* use this to find end the Mathematica-way *),
lineHeight*(lineCounter - 2)] (* use this to find end counting linebreaks *)
) &
]],
Dynamic@pos, Dynamic@lineCounter}

Mathematica graphics


There are two major drawbacks:




  1. This code cannot deal with implicit linebreaks: if maxLength is increased, there will be long lines that are wrapped by Pane, pushing further down the end of content. Such linewraps cannot be counted AFAIK.

  2. The lucky way how one can find the end-of-content position for the first time cannot be used at successive updates. This is exactly the problem for which I've posted this question. I've included a Print statement inside Refresh to check on successive evaluations during initialization. Interestingly, this code is evaluated 5 times during start, and results are either printed to the actual or to the Messages notebook. One can check that it is actually the 5th evaluation where the correct position value is assigned.



{1, 1000}    (* ==> to Messages notebook *)

{2, 1000} (* ==> to actual notebook *)

{3, 0.} (* ==> to Messages notebook *)


{4, 0.} (* ==> to actual notebook *)

{5, 16.} (* ==> to Messages notebook *)

Question: How can one figure out the actual vertical end-of-content position inside Pane whenever content is changed from the outside?




Comments

Popular posts from this blog

mathematical optimization - Minimizing using indices, error: Part::pkspec1: The expression cannot be used as a part specification

I want to use Minimize where the variables to minimize are indices pointing into an array. Here a MWE that hopefully shows what my problem is. vars = u@# & /@ Range[3]; cons = Flatten@ { Table[(u[j] != #) & /@ vars[[j + 1 ;; -1]], {j, 1, 3 - 1}], 1 vec1 = {1, 2, 3}; vec2 = {1, 2, 3}; Minimize[{Total@((vec1[[#]] - vec2[[u[#]]])^2 & /@ Range[1, 3]), cons}, vars, Integers] The error I get: Part::pkspec1: The expression u[1] cannot be used as a part specification. >> Answer Ok, it seems that one can get around Mathematica trying to evaluate vec2[[u[1]]] too early by using the function Indexed[vec2,u[1]] . The working MWE would then look like the following: vars = u@# & /@ Range[3]; cons = Flatten@{ Table[(u[j] != #) & /@ vars[[j + 1 ;; -1]], {j, 1, 3 - 1}], 1 vec1 = {1, 2, 3}; vec2 = {1, 2, 3}; NMinimize[ {Total@((vec1[[#]] - Indexed[vec2, u[#]])^2 & /@ R...

functions - Get leading series expansion term?

Given a function f[x] , I would like to have a function leadingSeries that returns just the leading term in the series around x=0 . For example: leadingSeries[(1/x + 2)/(4 + 1/x^2 + x)] x and leadingSeries[(1/x + 2 + (1 - 1/x^3)/4)/(4 + x)] -(1/(16 x^3)) Is there such a function in Mathematica? Or maybe one can implement it efficiently? EDIT I finally went with the following implementation, based on Carl Woll 's answer: lds[ex_,x_]:=( (ex/.x->(x+O[x]^2))/.SeriesData[U_,Z_,L_List,Mi_,Ma_,De_]:>SeriesData[U,Z,{L[[1]]},Mi,Mi+1,De]//Quiet//Normal) The advantage is, that this one also properly works with functions whose leading term is a constant: lds[Exp[x],x] 1 Answer Update 1 Updated to eliminate SeriesData and to not return additional terms Perhaps you could use: leadingSeries[expr_, x_] := Normal[expr /. x->(x+O[x]^2) /. a_List :> Take[a, 1]] Then for your examples: leadingSeries[(1/x + 2)/(4 + 1/x^2 + x), x] leadingSeries[Exp[x], x] leadingSeries[(1/x + 2 + (1 - 1/x...

What is and isn't a valid variable specification for Manipulate?

I have an expression whose terms have arguments (representing subscripts), like this: myExpr = A[0] + V[1,T] I would like to put it inside a Manipulate to see its value as I move around the parameters. (The goal is eventually to plot it wrt one of the variables inside.) However, Mathematica complains when I set V[1,T] as a manipulated variable: Manipulate[Evaluate[myExpr], {A[0], 0, 1}, {V[1, T], 0, 1}] (*Manipulate::vsform: Manipulate argument {V[1,T],0,1} does not have the correct form for a variable specification. >> *) As a workaround, if I get rid of the symbol T inside the argument, it works fine: Manipulate[ Evaluate[myExpr /. T -> 15], {A[0], 0, 1}, {V[1, 15], 0, 1}] Why this behavior? Can anyone point me to the documentation that says what counts as a valid variable? And is there a way to get Manpiulate to accept an expression with a symbolic argument as a variable? Investigations I've done so far: I tried using variableQ from this answer , but it says V[1...