# Graphing a non-linear inequality?

I'd love to automatically graph, on a number line, the solution to non-linear inequalities. Basically, the input would be an equation (e.g. x^2>=1) and the output would be a visual graph like the following example image. # Edit

As this question has been abandoned for more than 3 years so let me restrict the meaning of "non linear inequalities" to the following. I hope it is easy enough. • Without more precise specification than "nonlinear inequality", it is impossible to automate. – Paul Gaborit Feb 27 '19 at 21:44
• @TheInventorofGod I think you exaggerated with the easyness :) – CarLaTeX Feb 28 '19 at 17:26
• @TheInventorofGod Since you are now in charge of the question, what are the rules? External programs: yes or no? (If yes, then pgfplots with gnuplot does that out of the box.) And what are the a_i and b_i? (And why don't you ask a new question. This is not an implicit criticism, but a question.) – user121799 Mar 1 '19 at 4:37
• @TheInventorofGod ;-) Well, yes, they are real because otherwise the inequality doesn't make sense. My question was a more practical one: are answers supposed to use random numbers or what? – user121799 Mar 1 '19 at 4:45
• Also nonlinear equality come up in some proofs to show continuity, Limits, derivative and basically everything built off that. – marshal craft Mar 2 '19 at 15:49

## 6 Answers

Here is a proposal. (Math mode added, thanks to JouleV!)

\documentclass[tikz,border=3.14mm]{standalone}
\begin{document}
\begin{tikzpicture}
\draw [-stealth,thick] (-3.5,0) -- (3.5,0);
\foreach \X in {-3,...,3} {\draw (\X,0.1) -- (\X,-0.1) node[below]{$\X$}; }
\clip (-3.5cm,4pt) rectangle (3.5cm,6pt);
\draw[blue,line width=2pt] plot[variable=\x,domain=-3.5:3.5,samples=141]
({\x},{ifthenelse(\x*\x>=2%<- your condition
,5,20)*1pt});
\end{tikzpicture}
\end{document} And this shows how I cheat.

\documentclass[tikz,border=3.14mm]{standalone}
\begin{document}
\begin{tikzpicture}
\draw [-stealth,thick] (-3.5,0) -- (3.5,0);
\foreach \X in {-3,...,3} {\draw (\X,0.1) -- (\X,-0.1) node[below]{$\X$}; }
\draw[blue,line width=2pt] plot[variable=\x,domain=-3.5:3.5,samples=141]
({\x},{ifthenelse(\x*\x>=2%<- your condition
,5,20)*1pt});
\draw[red] (-3.5cm,4pt) rectangle (3.5cm,6pt) node[above left,font=\sffamily]{clip region};
\end{tikzpicture}
\end{document} Of course, one can cast this in a macro, if needed.

A TikZ-only solution that also draws arrows-heads and dots where necessary. It works with any inequality, not only rational functions, and requires only TikZ.

Update, casting the whole thing into something like a macro as suggested by @AndréC and with help from @marmot:

Code:

\documentclass[tikz,border=5mm]{standalone}
\usetikzlibrary{intersections,arrows.meta}
\makeatletter\long\def\ifnodedefined#1#2{\@ifundefined{pgf@sh@ns@#1}{}{#2}}\makeatother
\tikzset{
%line-styles
OO/.style={fill=blue,draw=blue,{Circle[width=1mm,length=1mm]}-{Circle[width=1mm,length=1mm]},shorten <= -0.5mm,shorten >= -0.5mm}, <O/.style={stealth-{Circle[width=1mm,length=1mm]},shorten <= 0mm}, O>/.style={{Circle[width=1mm,length=1mm]}-stealth,shorten >= 0mm}, <OO>/.style={stealth-stealth,shorten <= 0mm,shorten >= 0mm},every edge/.append style={OO},
%how to plot:
pics/.cd, plot inequality/.code n args={4}{
%all the paths we need to figure out the intersections etc.
\path[name path=conditionline] plot[variable=\x,domain={#2*1.01}:{#3*1.01},samples=300] ({\x},{ifthenelse(#1,{1+#4},{-1+#4})}); \path[name path=zeroline] (#2,#4) -- (#3,#4);\path[name path=start] (#2,{#4+1.1}) -- (#2,{#4+.9});\path[name path=end] (#3,{#4+1.1}) -- (#3,{#4+0.9});\path[name intersections={of=conditionline and start,name=startt}];\path[name intersections={of=conditionline and end,name=endd}];\path[name intersections={of=conditionline and zeroline,name=zerolinee}];\coordinate (intersection-0) at (#2,#4);
%draw lines, nots and arrows
\ifnum0\ifnodedefined{zerolinee-1}{1}\ifnodedefined{startt-1}{1}>0\draw[name intersections={of=zeroline and conditionline,total=\t,by=x}]\foreach \i [count=\s, evaluate=\s as \startswitch using iseven(\s+0\ifnodedefined{startt-1}{1})] in {0,...,{\t}}{\ifnum\i=\t coordinate (intersection-\s) at (#3,#4)\fi\if1\startswitch(intersection-\i) edge [\ifnodedefined{startt-1}{\ifnum\i=0<O\fi}\ifnodedefined{endd-1}{\ifnum\i=\t O>\fi}] (intersection-\s)\fi};\pgfnoderename{}{startt-1}\pgfnoderename{}{endd-1}\pgfnoderename{}{zerolinee-1}\fi}
}

\begin{document}
\begin{tikzpicture}

%real line
\draw[stealth-stealth] (-3.5,-.2) -- (3.5,-.2);\foreach \X in {-3,...,3} {\draw (\X,-0.1) -- (\X,-0.3) node[below]{$\X$}; }

\pic{plot inequality={0.05*(\x*\x-1)>sin(deg(\x*3)) }{-3.5}{3.5}{0}}; \node at (5.5,0) {$$\frac{(x^2-1)}{20}>\sin(3x)$$};
\pic{plot inequality={0.05*(\x*\x-1)<sin(deg(\x*3)) }{-3.5}{3.5}{1}}; \node at (5.5,1)  {$$\frac{(x^2-1)}{20}<\sin(3x)$$};
\pic{plot inequality={\x*\x>=1                      }{-3.5}{3.5}{2}}; \node at (5.5,2) {$$x^2\geq1$$};
\pic{plot inequality={0<1                           }{-3.5}{3.5}{3}}; \node at (5.5,3) {$$0<1$$};
\end{tikzpicture}
\end{document}


Result: [For the sake of brevity I'm deleting some intermediate solutions that are now obsolete. The current solution is more compact, more general and more precise than what I had before, but uses the same logic.]

The idea is to find all intersections of the indicator function for inequality with the 0-line. We then take those intersections and connect every second of with the next one. Depending on whether the inequality is true or false left side of the function, we start with the odd ones or the even ones. This logic is best illustrated by looking at the output from my first example, plotted against the left hand side of the inequality: Advantages:

• Works with any mathematical expression that can be evaluated by pgfmath
• Automatically draws dots or arrows to indicate whether or not the condition is true or false at the end of the displayed interval.
• Now (after first update) also correctly deals with cases where the condition is never or always true (ht @marmot)
• Now (after second update) test condition exactly at the two margins. No resolution-based uncertainty left.

Code-snippet with explanations:

% path that is at 1 where the condition is true and at -1 where it's  not
\path[name path=conditionline] plot[variable=\x,domain={#2*1.01}:{#3*1.01},samples=300] ({\x},{ifthenelse(#1,{1+#4},{-1+#4})});
% the zero-line
\path[name path=zeroline] (#2,#4) -- (#3,#4);
% two lines that intersect the conditionline if it is true at the margins of the interval
\path[name path=start] (#2,{#4+1.1}) -- (#2,{#4+.9});\path[name path=end] (#3,{#4+1.1}) -- (#3,{#4+0.9});
%testing whether the conditionline intersects these two lines and the zero line
\path[name intersections={of=conditionline and start,name=startt}];\path[name intersections={of=conditionline and end,name=endd}];\path[name intersections={of=conditionline and zeroline,name=zerolinee}];
coordinate at the left margin
\coordinate (intersection-0) at (#2,#4);
%draw lines, nots and arrows
%only draw something if the condition-line intersects the zero-line at all or if it's always true:
\ifnum0\ifnodedefined{zerolinee-1}{1}\ifnodedefined{startt-1}{1}>0
%loop over all intersections with the zero line (including a fictonal one for the left margin at (intersection-0):
\draw[name intersections={of=zeroline and conditionline,total=\t,by=x}]
\foreach \i [count=\s, evaluate=\s as \startswitch using iseven(\s+0\ifnodedefined{startt-1}{1})] in {0,...,{\t}}{
%if we're in the last round. create a coordinate for the right arrow-tip
\ifnum\i=\t coordinate (intersection-\s) at (#3,#4)\fi
%draw an edge from this intersection to the next, determine arrow heads depending on whether we are in the middle or the margins and whether the condition is true at the margins or not
\if1\startswitch(intersection-\i) edge [\ifnodedefined{startt-1}{\ifnum\i=0<O\fi}\ifnodedefined{endd-1}{\ifnum\i=\t O>\fi}] (intersection-\s)\fi};
%Delete all the old nodes used for testing in case another plot is drawn later
\pgfnoderename{}{startt-1}\pgfnoderename{}{endd-1}\pgfnoderename{}{zerolinee-1}
\fi
}
}


Old solution:

\documentclass[tikz,border=5mm]{standalone}
\usepackage{tikz}
\usetikzlibrary{intersections,arrows.meta}
\makeatletter
\long\def\ifnodedefined#1#2#3{\@ifundefined{pgf@sh@ns@#1}{#3}{#2}}
\makeatother
\begin{document}
\begin{tikzpicture}

%define condition
\path[name path=conditionline] plot[variable=\x,domain=-3.5:3.5,samples=150] ({\x},{ifthenelse(%
0.05*(\x*\x-1)+sin(deg(\x*3))>=0%<=any condition
,1,-1)});

%real line
\draw[stealth-stealth] (-3.5,-.2) -- (3.5,-.2);\foreach \X in {-3,...,3} {\draw (\X,-0.1) -- (\X,-0.3) node[below]{$\X$}; }

%helpers / testing if corners of interval are in or out
\path[name path=zeroline]  (-3.5,0) -- (3.5,0);
\path[name path=start] (-3.5,1) -- (-3.5,0);
\path[name path=end] (3.5,1) -- (3.5,0);
\path[name intersections={of=conditionline and start,name=startt}];
\path[name intersections={of=conditionline and end,name=endd}];

%draw lines, nots and arrows
\def\lasts{1}
\draw[fill=blue,draw=blue,name intersections={of=zeroline and conditionline,total=\t,by=x}]
\foreach[remember=\s as \lasts] \s in {1,...,{\t}}{
%if required, draw left arrow
\if\s1\ifnodedefined{startt-1}{  (intersection-1) edge [-stealth    ] (startt-1|-0,0)}\fi
%if required, draw right arrow
\if\s\t\ifnodedefined{endd-1}{  (intersection-\t) edge [-stealth] (endd-1|-0,0)}\fi
%draw intermediate lines
\ifodd\s
\ifnodedefined{startt-1}{(intersection-\s) -- (intersection-\lasts)}{}
\else
\ifnodedefined{startt-1}{}{(intersection-\s) -- (intersection-\lasts)}
\fi
%draw points
(intersection-\s) circle (2pt) node {}
};

\end{tikzpicture}
\end{document}


For completeness: I copied the code for the real line from @marmot's solution to this questions and \ifnodedefined from here

• The link you gave points to a question that marmot did not answer. Only Jake answered. – AndréC Mar 1 '19 at 15:30
• +1 I really like your idea. It would be interesting to make it a macro. – AndréC Mar 1 '19 at 15:35
• The question is 3 years old and as the PO has not specified anything, you are free to interpret its question as you wish. Moreover, the bonus is not given by the OP but by The Inventor of God. He has so far been able to leave comments, if he doesn't like what you do, he will say it. – AndréC Mar 1 '19 at 15:48
• +1 (If there is no intersection, the code throws an error, but this can be fixed easily.) – user121799 Mar 1 '19 at 19:12
• Well illustrated, fully reproducible and comprehensive answer. Love it. – gosz Mar 4 '19 at 21:04

I notice that your diagram has arrows, to indicate the interval continues on forever and filled circles, to indicate the endpoints satisfy the inequality. In order to achieve these features, which are important, I resorted to using the sagetex package to smooth out the complications. You can find the documentation on sagetex on CTAN here. This package relies on a computer algebra system (CAS) called Sage which is similar to Mathematica. Sage is opensource software which is free; the documentation is on the internet here. Sage is too big to include in a LaTeX distribution, so it needs to be installed locally on your machine (which can be a bit of a pain if you don't use Linux) or it can be accessed online with a free Cocalc account here. A Cocalc account is the quickest way to get started with Sage and sagetex. Here is the code:

\documentclass{article}
\usepackage[usenames,dvipsnames]{xcolor}
\usepackage{tikz}
\usepackage{sagetex}
\usetikzlibrary{backgrounds}
\usetikzlibrary{decorations}
\begin{document}
\begin{sagesilent}
# the function, f, should a nonconstant polynomial
#f = expand((x+5)^2)
#f = x^3-2*x-1
#f = x^3-x
#f = x^2+1
f = expand((x^2-5)*(x^2+x+1)*(x^2+2))
ineq = f<=0
epsilon = .01
root_list = f.roots(x)
real_roots = []
for root in root_list:
if root.is_real():
real_roots += [root.n(digits=3)]
### Order the roots from smallest to largest ###
roots = sorted(real_roots)
if len(roots)==0:   # if no roots, set a start and end
start = -2
end = 2
else:               # set start 2 below smallest root, end 2 above biggest root
start = roots-2
end = roots[len(real_roots)-1]+2
#####################TIKZ PICTURE SET UP ###########
output = r""
output += r"\begin{tikzpicture}"
##### Draw numberline ######
output += r"\draw (%s,0)--(%s,0);"%(start,end)
for i in range(start,end+1):
output += r"\draw (%s,.2)--(%s,-.2) node[below] {%s} ;"%(i,i,i)

##### Draw solution ######
if len(roots)==0:                       # No roots are found
if ineq(0):                        # all values satisfy inequality
output += r"\draw[thick,blue,<-] (%s,1)--(0,1);"%(start)
output += r"\draw[thick,blue,->] (0,1)--(%s,1);"%(end)
else:                                   # there are roots
if ineq(start):
output += r"\draw[thick,blue,<-] (%s,1)--(%s,1);"%(start,roots)
for i in range(0,len(roots)-1):
if ineq(roots[i]+epsilon):
output += r"\draw[blue] (%s,1)--(%s,1);"%(roots[i],roots[i+1])
if ineq(end):
output += r"\draw[thick,blue,->] (%s,1)--(%s,1);"%(roots[len(roots)-1],end)

########### Draw circles  #############
if len(roots)>0:
if "geq" in latex(ineq) or "leq" in latex(ineq):
for i in range(0,len(roots)):
if not(ineq(roots[i]-epsilon) and ineq(roots[i]+epsilon)):
output += r"\filldraw[blue] (%s,1) circle (0.07);"%(roots[i])
else:
for i in range(0,len(roots)):
output += r"\draw[blue] (%s,1) circle (0.07);"%(roots[i])
output += r"\end{tikzpicture}"
\end{sagesilent}
The real roots of $\sage{f}$ are approximately $\sage{roots}$.
Graphing the inequality $\sage{ineq}$ gives:\\\\
\sagestr{output}
\end{document}


Comments on the code: In sagesilent you're working with Python and # designates a comment as opposed to LaTeX where % sign is what you need. I've got several polynomials I used in testing commented out. After the function, f, is declared you'll need to change the next line, ineq = f<=0, to the inequality you want. The command root_list = f.roots(x) tells Sage to find the roots. However, since Sage is a CAS they need to be turned into numbers (such as sqrt(2) to 1.414) and then these roots are ordered from smallest to largest. This allows me to set start 2 smaller than the smallest root and end 2 bigger than the biggest root. I now know how big a numberline I need to show all the roots.

Next, the tikzpicture is set up as a string. Working with Sage is a 3 step process. First LaTeX is run, then Sage, then LaTeX is run again. Using a Cocalc account, pressing the Build button does all 3 at once. In order to use Sage calculations in my LaTeX commands, they have to show up after Sage has run. This is why a string is needed. That string gets placed into LaTeX code for the third run. The picture is broken into 3 pieces: the numberline (using start and end) followed by the solution, which also determines whether arrows are needed and then finally the circles are drawn shaded or not depending on the inequality. After that, sagesilent ends and LaTeX begins. Using the sage command I can get the function along with its roots.

Some negatives:

1. The input is not a rational function in its most general form. However, multiplying both sides by the denominator gives us the polynomial case being considered here (similar but not identical). The code is designed for a nonconstant polynomial on one side and 0 on the other.
2. The code requires you to set the function and then the inequality on the next line.
3. Requires Sage which is not part of LaTeX.

Some Positives:

1. The circles are shaded or unshaded as in your diagram.
2. The arrows are shown, as in your diagram.
3. The code uses the minimum and maximum roots to determine where to start graphing and where to finish graphing. No trial and error needed.

4. function input is more natural; e.g. f = x^2+1 versus \x*\x+1. Additionally, you can write f = expand((x^2-5)(x^2+x+1)(x^2+2)) and Sage will do the expansion.

5. Code works in case of no distance between intervals; e.g. f(x)=x^2 where the intervals are -infty to 0 and 0 to infinity

6. Sage can output the roots.

EDIT: Code fixed to account for no roots case marmot mentioned and additional positive and negative points added or revised.

EDIT 2: In response to your question below, "Is it possible to show the exact values of roots as labels on the number lines and remove the distracting integer labels?" the short answer is yes. I've revised code to mark roots on the numberline instead of numbers.

\documentclass{article}
\usepackage[usenames,dvipsnames]{xcolor}
\usepackage{tikz}
\usepackage{sagetex}
\usetikzlibrary{backgrounds}
\usetikzlibrary{decorations}
\begin{document}
\begin{sagesilent}
# the function, f, should a nonconstant polynomial
#f = expand((x+5)^2)
#f = x^3-2*x-1
#f = x^3-x
#f = x^2+1
f = expand((x^2-5)*(x^2+x+1)*(x^2+2))
ineq = f>0
epsilon = .01
root_list = f.roots(x)
real_roots = []
Eroots = []
for root in root_list:
if root.is_real():
real_roots += [root.n(digits=3)]
Eroots += [root]
### Order the roots from smallest to largest ###
roots = sorted(real_roots)
Eroots = sorted(Eroots)
if len(roots)==0:   # if no roots, set a start and end
start = -2
end = 2
else:               # set start 2 below smallest root, end 2 above    biggest root
start = roots-2
end = roots[len(real_roots)-1]+2
#####################TIKZ PICTURE SET UP ###########
output = r""
output += r"\begin{tikzpicture}"
##### Draw numberline ######
output += r"\draw[<->] (%s,0)--(%s,0);"%(start,end)
if len(roots)>0:
for i in range(0,len(Eroots)):
output += r"\draw (%s,.2)--(%s,-.2) node[below] {$%s$} ;"%(roots[i],roots[i],latex(Eroots[i]))

##### Draw solution ######
if len(roots)==0:                       # No roots are found
if ineq(0):                        # all values satisfy inequality
output += r"\draw[thick,blue,<-] (%s,1)--(0,1);"%(start)
output += r"\draw[thick,blue,->] (0,1)--(%s,1);"%(end)
else:                                   # there are roots
if ineq(start):
output += r"\draw[thick,blue,<-] (%s,1)--(%s,1);"%(start,roots)
for i in range(0,len(roots)-1):
if ineq(roots[i]+epsilon):
output += r"\draw[blue] (%s,1)--(%s,1);"%(roots[i],roots[i+1])
if ineq(end):
output += r"\draw[thick,blue,->] (%s,1)--(%s,1);"%(roots[len(roots)-1],end)

########### Draw circles  #############
if len(roots)>0:
if "geq" in latex(ineq) or "leq" in latex(ineq):
for i in range(0,len(roots)):
if not(ineq(roots[i]-epsilon) and ineq(roots[i]+epsilon)):
output += r"\filldraw[blue] (%s,1) circle (0.07);"%(roots[i])
else:
for i in range(0,len(roots)):
output += r"\draw[blue] (%s,1) circle (0.07);"%(roots[i])
output += r"\end{tikzpicture}"
\end{sagesilent}
The real roots of $\sage{f}$ are $\sage{Eroots}$ which are approximately
$\sage{roots}$. Graphing the inequality $\sage{ineq}$ gives:\\\\
\sagestr{output}
\end{document}


But there is a problem. Although the output looks fine in many cases: it won't look so good if the roots are complicated and too close together. One root is printing over the other: Therefore, I've kept the first version of code. It seems a bit more robust to me.

• Is it possible to show the exact values of roots as labels on the number lines and remove the distracting integer labels? – Money Sets You Free Mar 2 '19 at 19:48
• Yes, I've added the code for that in my answer below the original. There is an issue depending upon what you're graphing. I've given a sample screenshot. – DJP Mar 2 '19 at 23:01

1D is a special case of 2D

\documentclass[tikz]{standalone}
\pgfdeclarefunctionalshading{inequality}{\pgfpoint{-25bp}{-25bp}}{\pgfpoint{25bp}{25bp}}{}{
% x y
2 copy      % x y x y
2 copy      % x y x y x y
exch atan   % x y x y theta
3 1 roll    % x y theta x y
dup mul exch dup mul
add sqrt    % x y theta r
20 mul      % x y theta 20r
add sin     % x y sin(theta+20r)
3 1 roll    % sin(theta+20r) x y
200 mul sin exch 200 mul sin
mul         % sin(theta+20r) sin(200x)sin(200y)
gt          % sin(theta+20r) > sin(200x)sin(200y) ?
{0 0 0}{1 1 1}ifelse
}
\begin{document}
\tikz\path[shading=inequality](-10,-10)rectangle(10,10);
\end{document} • I did not understand your answer. Can you explain it? – AndréC Mar 1 '19 at 5:16
• @TheInventorofGod My question is addressed to Symbol 1, I do not understand his answer. You and Symbol 1 are the same person? – AndréC Mar 1 '19 at 10:42
• @AndréC OP asks for a method to depict a subset of real numbers defined by an inequality. And I give an answer that depicts a subset of R^2 defined by an inequality. My answer is more general since subsets of R are subsets of R^2. – Symbol 1 Mar 1 '19 at 18:37
• When I see your pretty graphic, I see a spiral. But there is no indication how all the solutions can be read. A straight line representation is much easier to read and understand. – AndréC Mar 1 '19 at 18:52
• The spiral is defined by sin(theta+20r) > sin(200x)sin(200y). For an 1D inequality A(x) > B(x), it could be translate into A(x) > B(x) and -1 < y < 1. Hence my solution applies. – Symbol 1 Mar 1 '19 at 18:55
\documentclass[border=15pt]{standalone}
\usepackage{pst-plot}
\makeatletter
\def\specialPlot{\pst@object{specialPlot}}
\def\specialPlot@i(#1,#2)#3#4{%
\pst@killglue
\addto@par{algebraic}%
\begin@OpenObj
\addto@pscode{
#2 #1 sub \psk@plotpoints div /dx ED
0 0.25 \tx@ScreenCoor translate
/getY
\ifPst@algebraic (#3) tx@AlgToPs begin AlgToPs end cvx
\else { #3 } \fi def
#1 0 \tx@ScreenCoor moveto
#1 dx add dx #2 {
dup /x ED
getY 0 #4 { 0 \tx@ScreenCoor lineto }
{ 0 \tx@ScreenCoor moveto } ifelse
}  for
}%
\end@OpenObj
\ignorespaces
}
\makeatother

\begin{document}
\begin{pspicture}(-3.5,-0.5)(5,1.75)
\psaxes[linecolor=gray,yAxis=false,subticks=5,ticksize=0 5pt](0,0)(-3.2,0)(3.2,0)%
\psset{linewidth=1.5pt}
\specialPlot[linecolor=red](-3,3){x^2-1}{ge}
\rput[lb](3.5,0.1){$x^2-1\ge 0$}
\rput(0,0.5){\specialPlot[linecolor=red](-3,3){x^2-1}{lt}}
\rput[lb](3.5,0.6){$x^2-1< 0$}
\rput(0,1){\specialPlot[linecolor=blue](-3,3){x^3-x}{ge}}
\rput[lb](3.5,1.1){$x^3-x\ge 0$}
\rput(0,1.5){\specialPlot[linecolor=blue](-3,3){x^3-x}{lt}}
\rput[lb](3.5,1.6){$x^3-x< 0$}
\end{pspicture}

\end{document} • Please tell me know the difference between ED and def ? – user173875 Mar 1 '19 at 12:33
• /ED {exch def } def  is only an abbreviation – user2478 Mar 1 '19 at 12:50

I propose a solution using only Lua as intermediate tool, requiring only LuaLaTeX as engine. Since the the inequality was restricted to products of 2-degree-polynomials, we can numerically solve the inequalities by the quadratic equation. We collect all roots of all polynomials, and analyze the resulting intervals between the roots. From this, we let TikZ draw the final diagram.

Here are some test runs. Red circles indicate singularities, i.e. undefined points of the polynomial where the denominator is zero. Blue circles indicate that the point is not part of the solution set. Advantages:

• Only requires LuaLaTeX and the packages tikz and luacode, nothing else, to generate the diagrams.
• Circles and arrows are shown, as in your diagram.
• Roots are represented as dots or circles, depending on whether the root is part of the solution set, or resp. not.
• Correctly handles singularities of the rational function.

Disadvantages:

• As opposed to DJP's answer using Sage and symbolic solving, the accuracy of this solution is limited to Lua's floating-point accuracy.
• Similarly, the inequality is restricted to products of quadratic polynomials, as there is no general numerical method to root finding.
• The function needs to be entered via coefficients. For example, the code ineq.tikz_ineq({{-3,2,10},{3,-1,0}}, {{4,5,-5}}, '<='), generates the first diagram.

This code generated the diagrams, using only LuaLaTeX (the file ineq.lua is placed in the same directory as the tex-file):

\documentclass[tikz,border=20pt]{standalone}
\usepackage{luacode}
\directlua{ineq = require('ineq')}
\begin{document}
\begin{luacode}
local nom_polys = {{-3,2,10},{3,-1,0}}
local denom_polys = {{4,5,-5}}
ineq.tikz_ineq(nom_polys, denom_polys, '<=')
\end{luacode}

\begin{luacode}
local nom_polys = {{0,0,0}}
local denom_polys = {{1,0,0}}
ineq.tikz_ineq(nom_polys, denom_polys, '>=')
\end{luacode}

\begin{luacode}
local nom_polys = {{-3,0,1}}
local denom_polys = {{1,0,0}}
ineq.tikz_ineq(nom_polys, denom_polys, '<=')
\end{luacode}

\begin{luacode}
local nom_polys = {{1,0,0}}
local denom_polys = {{0,0,1}}
ineq.tikz_ineq(nom_polys, denom_polys, '>=')
\end{luacode}

\begin{luacode}
local nom_polys = {{1,0,-1},{0,1,0}}
local denom_polys = {{0,0,1}}
ineq.tikz_ineq(nom_polys, denom_polys, '<')
\end{luacode}
\end{document}


ineq.lua

function tikz_ineq(num_polys, denom_polys, ineq_str)

local function ineq(v)
if ineq_str == '<=' then return v <= 0 end
if ineq_str == '<' then return v < 0 end
if ineq_str == '>=' then return v >= 0 end
if ineq_str == '>' then return v > 0 end
end

local function fullpoly(x)
local num, denom = 1, 1
for i,p in ipairs(num_polys) do
local a,b,c = unpack(p)
num = num * (a*x*x + b*x + c)
end
for i,p in ipairs(denom_polys) do
local a,b,c = unpack(p)
denom = denom * (a*x*x + b*x + c)
end

return num/denom
end

local function roots(c)
local a,b,c = unpack(c)

if a ~= 0 then
if (b*b - 4*a*c) >= 0 then
local root1 = (-b + math.sqrt(b*b - 4*a*c))/(2*a)
local root2 = (-b - math.sqrt(b*b - 4*a*c))/(2*a)
return {root1, root2}
else return {} end
elseif b ~= 0 then
return { -c/b }
else return {} end
end

local function f(x)
return string.format('% 0.2f',x)
end

local function draw_point(x,include)
if include then
tex.print('\\draw[blue,fill=blue] ('..f(x)..',0.2) circle (.04);')
else
tex.print('\\draw[blue,fill=white] ('..f(x)..',0.2) circle (.04);')
end
end

-- collect roots
local roots_num = {}
local roots_denom = {}
local all_roots = {}

for i,p in ipairs(num_polys) do
for j,x in ipairs(roots(p)) do
table.insert(roots_num, x)
table.insert(all_roots, x)
end
end
for i,p in ipairs(denom_polys) do
for j,x in ipairs(roots(p)) do
table.insert(roots_denom, x)
table.insert(all_roots, x)
end
end

table.sort(all_roots)

-- test if point is root of denominator -> singularitiy
local jumppoint = {}
for i=1,#all_roots do
jumppoint[i] = false
for j=1,#roots_denom do
if all_roots[i] == roots_denom[j] then
jumppoint[i] = true
end
end
end

local min = all_roots
local max = all_roots[#all_roots]

tex.print('\\begin{tikzpicture}[scale=2]')
local left_boundary = math.floor(math.min(-3, min or 0))
local right_boundary = math.ceil(math.max(3, max or 0))
tex.print('\\draw [-stealth,thick] ('..f(left_boundary-0.5)..',0) -- ('..f(right_boundary+0.5)..',0); \\foreach \\X in {' .. left_boundary ..',...,'..right_boundary..'} {\\draw (\\X,0.1) -- (\\X,-0.1) node[below]{$\\X$}; }')

if #all_roots == 0 then -- no roots, test globally
if ineq(fullpoly(0)) then
tex.print('\\draw[blue,<->] ('..f(left_boundary-0.5)..',0.2) -- ('..f(right_boundary+0.5)..',0.2);')
end
else
for i=0,#all_roots do -- iterate trough each interval

if i==0 then -- (-inf,min)
if ineq(fullpoly(min-10)) then
local left = math.min(left_boundary-0.5, min-0.5)
tex.print('\\draw[blue,<-] ('..f(left)..',0.2) -- ('..f(min)..',0.2);')
draw_point(min, ineq(0) and not jumppoint)
end
elseif i==#all_roots then -- (max,inf)
if ineq(fullpoly(max+10)) then
local right = math.max(right_boundary+0.5, max+0.5)
tex.print('\\draw[blue,->] ('..f(max)..',0.2) -- ('..f(right)..',0.2);')
draw_point(max, ineq(0) and not jumppoint[#all_roots])
end
else -- (all_roots[i],all_roots[i+1])
local left = all_roots[i]
local right = all_roots[i+1]
if ineq(fullpoly((left + right)/2)) then
tex.print('\\draw[blue,-] ('..f(left)..',0.2) -- ('..f(right)..',0.2);')
draw_point(left, ineq(0) and not jumppoint[i])
draw_point(right, ineq(0) and not jumppoint[i+1])
end
end

-- optional: highlight singularities
if i>0 and jumppoint[i] then
tex.print('\\draw[red] ('..f(all_roots[i])..',0.2) circle (.06);')
end

end
end

-- optional: write inequality
local num = ''
local denom = ''
for i,c in ipairs(num_polys) do
num = num .. string.format('(%dx^2%+dx%+d)', c, c, c)
end
for i,c in ipairs(denom_polys) do
denom = denom .. string.format('(%dx^2%+dx%+d)', c, c, c)
end
if ineq_str == '>=' then ineq_str = '\\geq' end
if ineq_str == '<=' then ineq_str = '\\leq' end
tex.print('\\node at (0, -1) {$\\displaystyle\\frac{'..num..'}{'..denom..'}'..ineq_str..'0$}; ')

tex.print('\\end{tikzpicture}')

end

return { tikz_ineq = tikz_ineq }