pgfplots: Fläche zwischen zwei Funktionen einfärben

Tabellen und Grafiken erstellen und anordnen


Mac-Cherony
Forum-Anfänger
Forum-Anfänger
Beiträge: 48
Registriert: Sa 30. Apr 2011, 11:39

pgfplots: Fläche zwischen zwei Funktionen einfärben

Beitrag von Mac-Cherony »

Hallo,

ich möchte mit pgfplots die Fläche zwischen zwei Funktionen einfärben und zwar je nachdem ob y1>y2 oder umgekehrt ist. Also z.B. eine rote Füllung wenn y1<y2 ist und eine grüne wenn y1>y2 ist.

Die Daten habe ich als Textdatei vorliegen. Ich habe ein lauffähiges Minimalbeispiel erarbeitet. Bisher ist es mir leider nur gelungen die Fläche zwischen den Plots mit nur einer Farbe zu füllen. Kann mir hier Jemand weiterhelfen?
\documentclass{standalone}
\usepackage{pgfplots}
\usepackage{filecontents}

\begin{filecontents}{plotdata.dat}
-3.5 0.350783228 -0.343633445
-3.25 0.108195135 -0.107984166
-3 -0.141120008 0.140652077
-2.75 -0.381660992 0.372462462
-2.5 -0.598472144 0.563380821
-2.25 -0.778073197 0.701908324
-2 -0.909297427 0.789072344
-1.75 -0.983985947 0.83271103
-1.5 -0.997494987 0.840114882
-1.25 -0.948984619 0.812824456
-1 -0.841470985 0.745624142
-0.75 -0.68163876 0.630066434
-0.5 -0.479425539 0.461269555
-0.25 -0.247403959 0.244887792
0 0 0
0.25 0.247403959 -0.244887792
0.5 0.479425539 -0.461269555
0.75 0.68163876 -0.630066434
1 0.841470985 -0.745624142
1.25 0.948984619 -0.812824456
1.5 0.997494987 -0.840114882
1.75 0.983985947 -0.83271103
2 0.909297427 -0.789072344
2.25 0.778073197 -0.701908324
2.5 0.598472144 -0.563380821
2.75 0.381660992 -0.372462462
3 0.141120008 -0.140652077
3.25 -0.108195135 0.107984166
3.5 -0.350783228 0.343633445
\end{filecontents}


\begin{document}
\begin{tikzpicture}
\begin{axis} []
 \addplot [] table[x index={0}, y index={1}] {plotdata.dat};
 \addplot [] table[x index={0}, y index={2}] {plotdata.dat};
 
\addplot [draw=none,stack plots=y,forget plot] table [y index={1}] {plotdata.dat};
\addplot [draw=none, fill=gray, opacity=.5,stack plots=y] table [y expr=\thisrowno{2}-\thisrowno{1}] {plotdata.dat}\closedcycle;

\end{axis}
\end{tikzpicture}
\end{document}
Danke schonmal für die Hilfe!

esdd
Forum-Meister
Forum-Meister
Beiträge: 2561
Registriert: So 7. Feb 2010, 16:36

Beitrag von esdd »

Schönes Minimalbeispiel, aber das Problem selbst ist schwierig. Ich glaub nicht, dass man das wirklich automatisch erreichen kann.

Hier ist mal ein Vorschlag, wie man das vielleicht machen kann:
\documentclass{standalone} 
\usepackage{pgfplots} 
\pgfplotsset{compat=1.9}
\usetikzlibrary{intersections}
\usepackage{filecontents} 

\begin{filecontents}{plotdata.dat} 
 -3.5 0.350783228 -0.343633445 
 -3.25 0.108195135 -0.107984166 
 -3 -0.141120008 0.140652077 
 -2.75 -0.381660992 0.372462462 
 -2.5 -0.598472144 0.563380821 
 -2.25 -0.778073197 0.701908324 
 -2 -0.909297427 0.789072344 
 -1.75 -0.983985947 0.83271103 
 -1.5 -0.997494987 0.840114882 
 -1.25 -0.948984619 0.812824456 
 -1 -0.841470985 0.745624142 
 -0.75 -0.68163876 0.630066434 
 -0.5 -0.479425539 0.461269555 
 -0.25 -0.247403959 0.244887792 
 0 0 0 
 0.25 0.247403959 -0.244887792 
 0.5 0.479425539 -0.461269555 
 0.75 0.68163876 -0.630066434 
 1 0.841470985 -0.745624142 
 1.25 0.948984619 -0.812824456 
 1.5 0.997494987 -0.840114882 
 1.75 0.983985947 -0.83271103 
 2 0.909297427 -0.789072344 
 2.25 0.778073197 -0.701908324 
 2.5 0.598472144 -0.563380821 
 2.75 0.381660992 -0.372462462 
 3 0.141120008 -0.140652077 
 3.25 -0.108195135 0.107984166 
 3.5 -0.350783228 0.343633445 
\end{filecontents} 

\begin{document} 
\begin{tikzpicture}
\pgfplotsset{xmin=-4.25,xmax=4.25,ymin=-1.25,ymax=1.25}
\begin{axis}
  \addplot [name path global=plot1] table[x index={0}, y index={1}] {plotdata.dat}; 
  \addplot [name path global=plot2] table[x index={0}, y index={2}] {plotdata.dat};
  %% Schnittpunkte der beiden Plots ermitteln:
  \path [name intersections={of=plot1 and plot2,name=i, total=\t}]\foreach \s in {1,...,\t}{(i-\s)
   %node{\s} %Schnittpunktnummern anzeigen
  };
  \coordinate (O) at (rel axis cs:0,0);\coordinate (P) at (rel axis cs:1,1);
\end{axis} 
% Färben:
\foreach \n/\m/\farbe in {O/i-1/green,i-1/i-2/red,i-5/i-6/green,i-6/P/red}{%
  \begin{axis}
    \clip(O-|\n)rectangle(P-|\m);
    \addplot [draw=none,stack plots=y,forget plot] table [y index={1}] {plotdata.dat}; 
    \addplot [draw=none,stack plots=y, fill=\farbe, opacity=.5] table [y expr=\thisrowno{2}-\thisrowno{1}] {plotdata.dat}\closedcycle; 
  \end{axis}
}
\end{tikzpicture} 
\end{document} 
Aus irgendwelchen Gründen werden insgesamt 6 Schnittpunkte gefunden, von denen vier in der Mitte aufeinanderliegen ermittelt. Mit der Option smooth bei den Plots sind es dagegen nur die 3 Schnittpunkte, die wir auch sehen:
\begin{tikzpicture}
\pgfplotsset{xmin=-4.25,xmax=4.25,ymin=-1.25,ymax=1.25}
\begin{axis}
  \addplot [name path global=plot1,smooth] table[x index={0}, y index={1}] {plotdata.dat}; 
  \addplot [name path global=plot2,smooth] table[x index={0}, y index={2}] {plotdata.dat};
  %% Schnittpunkte der beiden Plots ermitteln:
  \path [name intersections={of=plot1 and plot2,name=i, total=\t}]\foreach \s in {1,...,\t}{(i-\s)
   %node{\s} %Schnittpunktnummern anzeigen
  };
  \coordinate (O) at (rel axis cs:0,0);\coordinate (P) at (rel axis cs:1,1);
\end{axis} 
% Färben:
\foreach \n/\m/\farbe in {O/i-1/green,i-1/i-2/red,i-2/i-3/green,i-3/P/red}{%
  \begin{axis}
    \clip(O-|\n)rectangle(P-|\m);
    \addplot [draw=none,stack plots=y,smooth,forget plot] table [y index={1}] {plotdata.dat}; 
    \addplot [draw=none,stack plots=y,smooth,fill=\farbe, opacity=.5] table [y expr=\thisrowno{2}-\thisrowno{1}] {plotdata.dat}\closedcycle; 
  \end{axis}
}
\end{tikzpicture} 
Gruß
Elke

Mac-Cherony
Forum-Anfänger
Forum-Anfänger
Beiträge: 48
Registriert: Sa 30. Apr 2011, 11:39

Beitrag von Mac-Cherony »

Vielen Dank für deinen Ansatz Elke, leider funktioniert das nicht immer so allgemein für andere Werte.
Als Übergangslösung generiere ich mir nun die Flächen mit Matlab, da ich meine Daten auch dort erzeuge.

Mit dem Ergebnis stimmt allerdings auch noch etwas nicht. Interessanterweise wird für jede Fläche die ich plotte auch ein Punkt bei y=0 geplottet, der allerdings nicht in den Daten vorhanden ist. Gibt es eine einfache Lösung dafür diesen Punkt nicht zu plotten, so dass wirklich nur die Fläche gefüllt wird?
Als vergleich habe ich einen Plot eingefügt, der direkt in pgfplots erzeugt wird.

Hier ist der Matlabcode mit dem ich die Daten generiere:

x  = [-2:0.01:2]';
y1 = [0.2*x.^3-.05*x.^2+0.2];
y2 = [0.55*x+.13];
Data=[x(:) y1(:) y2(:)];
Diff=y1-y2;

dlmwrite('Data.dat', Data, 'delimiter','\t');
Index=['A';'B';'C';'D';'E';'F';'G';'H';];

VZW = find(diff(sign(Diff)) ~=0);
 if isempty(VZW)==1;
     VZW=length(x);
 else
 end

X=x(1:VZW(1));
Y1=y1(1:VZW(1));
Y2=y2(1:VZW(1));
X = [X,flipud(X)];                
Y = [Y1,flipud(Y2)];

if Diff(1)>0       
        filename=strcat('Data',Index(1,:),'_pos.dat');
        dlmwrite(filename, [X(:) Y(:)], 'delimiter','\t');       
else      
        filename=strcat('Data',Index(1,:),'_neg.dat');
        dlmwrite(filename, [X(:) Y(:)], 'delimiter','\t');        
end

VZW = find(diff(sign(Diff)) ~=0);
 if isempty(VZW)==1;
     VZW=length(x)-1;
 else
 end
 
for n=1:length(VZW)
       
        if n<length(VZW)  

             X=x(VZW(n):VZW(n+1));
             Y1=y1(VZW(n):VZW(n+1));
             Y2=y2(VZW(n):VZW(n+1));
             X = [X,flipud(X)];                
             Y = [Y1,flipud(Y2)];

             if Diff(VZW(n)+1)>0
                filename=strcat('Data',Index(n+1,:),'_pos.dat');
                dlmwrite(filename, [X(:) Y(:)], 'delimiter','\t');
             else
                filename=strcat('Data',Index(n+1,:),'_neg.dat');
                dlmwrite(filename, [X(:) Y(:)], 'delimiter','\t');
             end
        else
             X=x(VZW(n):end);
             Y1=y1(VZW(n):end);
             Y2=y2(VZW(n):end);
             X = [X,flipud(X)];                
             Y = [Y1,flipud(Y2)];

             if Diff(VZW(n)+1)>0
                filename=strcat('Data',Index(n+1,:),'_pos.dat');
                dlmwrite(filename, [X(:) Y(:)], 'delimiter','\t');
             else
                filename=strcat('Data',Index(n+1,:),'_neg.dat');
                dlmwrite(filename, [X(:) Y(:)], 'delimiter','\t');
             end
        end        
end  
und hier das Minimalbeispiel in LaTeX:
\documentclass{standalone} 
\usepackage{pgfplots}

\begin{document}

\begin{tikzpicture} 
\begin{axis} [xmin=-2,xmax=2] 
 
 \addplot [draw=none, fill=red] file {DataA_neg.dat} \closedcycle;
 \addplot [draw=none, fill=green] file {DataB_pos.dat} \closedcycle;
 \addplot [draw=none, fill=red] file {DataC_neg.dat} \closedcycle;
 \addplot [draw=none, fill=green] file {DataD_pos.dat} \closedcycle;
 \addplot [] table[x index={0}, y index={1}] {Data.dat}; 
 \addplot [blue] table[x index={0}, y index={2}] {Data.dat}; 
 \end{axis} 
\end{tikzpicture}

\begin{tikzpicture}

    \begin{axis}[xmin=-2,xmax=2,domain=-2:2,samples=50,stack plots=y]
        \addplot+[mark=none, blue] {.55*x+.13};
        \addplot+[mark=none,fill=green,draw=black] {max(.2*x^3-.05*x^2+.2-(.55*x+.13),0)} \closedcycle;
        \addplot+[mark=none,fill=red,draw=black] {min(.2*x^3-.05*x^2+.2-(.55*x+.13),0)} \closedcycle;
    \end{axis}

\end{tikzpicture}

\end{document}

Mac-Cherony
Forum-Anfänger
Forum-Anfänger
Beiträge: 48
Registriert: Sa 30. Apr 2011, 11:39

Beitrag von Mac-Cherony »

Nach etwas stöbern im pgfplots-manual habe ich das Problem gelöst.
\documentclass{standalone} 
\usepackage{pgfplots} 

\begin{document} 

\begin{tikzpicture} 
\begin{axis} [xmin=-2,xmax=2] 
  
 \addplot [draw=none, fill=red] file {DataA_neg.dat} --cycle; 
 \addplot [draw=none, fill=green] file {DataB_pos.dat} --cycle; 
 \addplot [draw=none, fill=red] file {DataC_neg.dat} --cycle; 
 \addplot [draw=none, fill=green] file {DataD_pos.dat} --cycle; 
 \addplot [] table[x index={0}, y index={1}] {Data.dat}; 
 \addplot [blue] table[x index={0}, y index={2}] {Data.dat}; 
 \end{axis} 
\end{tikzpicture} 

\begin{tikzpicture} 

    \begin{axis}[xmin=-2,xmax=2,domain=-2:2,samples=50,stack plots=y] 
        \addplot+[mark=none, blue] {.55*x+.13}; 
        \addplot+[mark=none,fill=green,draw=black] {max(.2*x^3-.05*x^2+.2-(.55*x+.13),0)} \closedcycle; 
        \addplot+[mark=none,fill=red,draw=black] {min(.2*x^3-.05*x^2+.2-(.55*x+.13),0)} \closedcycle; 
    \end{axis} 

\end{tikzpicture} 

\end{document} 

feuersaenger
Forum-Fortgeschrittener
Forum-Fortgeschrittener
Beiträge: 91
Registriert: Mi 5. Okt 2011, 18:24
Wohnort: Rheinbach

Beitrag von feuersaenger »

Hallo,

Version 1.10 von pgfplots ist gerade frisch rausgekommen, und es hat als Hauptmerkmal eine Loesung fuer das Problem, die Flaeche zwischen zwei beliebigen Plots einzufaerben.

Alle alten Loesungen sind immer noch gueltig, aber dies hier kann die Aufgabe gehoerig vereinfachen:
\documentclass{standalone}
\usepackage{pgfplots}

\pgfplotsset{compat=1.10}
\usepgfplotslibrary{fillbetween}
\usepackage{filecontents}

\begin{filecontents}{plotdata.dat}
-3.5 0.350783228 -0.343633445
-3.25 0.108195135 -0.107984166
-3 -0.141120008 0.140652077
-2.75 -0.381660992 0.372462462
-2.5 -0.598472144 0.563380821
-2.25 -0.778073197 0.701908324
-2 -0.909297427 0.789072344
-1.75 -0.983985947 0.83271103
-1.5 -0.997494987 0.840114882
-1.25 -0.948984619 0.812824456
-1 -0.841470985 0.745624142
-0.75 -0.68163876 0.630066434
-0.5 -0.479425539 0.461269555
-0.25 -0.247403959 0.244887792
0 0 0
0.25 0.247403959 -0.244887792
0.5 0.479425539 -0.461269555
0.75 0.68163876 -0.630066434
1 0.841470985 -0.745624142
1.25 0.948984619 -0.812824456
1.5 0.997494987 -0.840114882
1.75 0.983985947 -0.83271103
2 0.909297427 -0.789072344
2.25 0.778073197 -0.701908324
2.5 0.598472144 -0.563380821
2.75 0.381660992 -0.372462462
3 0.141120008 -0.140652077
3.25 -0.108195135 0.107984166
3.5 -0.350783228 0.343633445
\end{filecontents}


\begin{document}
\begin{tikzpicture}
\begin{axis} []
 \addplot [name path=A] table[x index={0}, y index={1}] {plotdata.dat};
 \addplot [name path=B] table[x index={0}, y index={2}] {plotdata.dat};

		\addplot fill between[
			of=A and B,
			split,
			every odd segment/.style={red,left color=red,right color=white},
			every even segment/.style= {green,left color=green, right color=white},
		];
 
\end{axis}
\end{tikzpicture}

\end{document}
Die Loesung basiert auf
\usepgfplotslibrary{fillbetween}
, was die Syntax
\addplot fill between[of=<first> and <second>]
aktiviert. In diesem Fall wollen wir einzelne Segmente des Schnitts identifizieren. Dazu muessen wir diese erstmal ausrechnen lassen, was mithilfe der Option
split
getan wird. Anschliessend koennen wir individuelle Styles fuer
every odd segment
und
every even segment
definieren und nutzen. Zu beachten ist, dass das erste Segmente den Index 0 hat.

Mit liebem Gruss

Christian

PS
Leider funktioniert der Bild upload nicht "Upload Error: Kann das Attachment ./files/4_205.png nicht hochladen. " . Weiss jemand, wie ich bilder anhaengen kann?
Zuletzt geändert von feuersaenger am Di 4. Mär 2014, 20:05, insgesamt 1-mal geändert.

Stamm-

Versionsnummer

Beitrag von Stamm- »

feuersaenger hat geschrieben:[…] Version 1.0 von pgfplots ist gerade frisch rausgekommen […]
Du meinst sicherlich 1.10, als Nachfolger von 1.9. momentan noch nicht auf CTAN erhältlich. Aber das gibt sich bald.

feuersaenger
Forum-Fortgeschrittener
Forum-Fortgeschrittener
Beiträge: 91
Registriert: Mi 5. Okt 2011, 18:24
Wohnort: Rheinbach

Beitrag von feuersaenger »

Richtig, ich meinte 1.10 - danke!

Die CTAN Mirrorscripte laufen schon (eine Bestaetigung habe ich heute frueh erhalten.

Ich habe meine Antwort editiert sodass sie die richtige Versionsnummer enthaelt.

Mit liebem Gruss

Christian

unit
Forum-Newbie
Forum-Newbie
Beiträge: 3
Registriert: Mi 12. Mär 2014, 10:07

Beitrag von unit »

Hallo,

der Befehl fill between ist genau das, was ich brauche, danke dafür Christian! Ich habe pgfplots neu installiert um das neuste Package zu kriegen (ich nutze Win7 und Miktex 2.9) und diesen Code aus der Dokumentation von pgfplots (S. 365) getestet:
\documentclass{standalone}
 
\usepackage{pgfplots}
\pgfplotsset{compat=1.10} 
\usepgfplotslibrary{fillbetween}
\usetikzlibrary{pgfplots.fillbetween}
 
\begin{document}
 
test
 
\begin{tikzpicture}
	\begin{axis}
		\addplot[blue,name path=A,domain=0:1] {sqrt(x)};
		\addplot[red, name path=B,domain=0:1] {sqrt(x/2)};
		\addplot[gray] fill between[of=A and B];
	\end{axis}
\end{tikzpicture}

\end{document}


Ich bekomme jedoch die Fehlermeldung

"! Package pgfkeys Error: Choice '1.10' unknown in key '/pgfplots/compat/anchors'. I am going to ignore this key."

nicht nur für anchors, aber für diverse andere keys auch. Laut Miktex Package Manager ist pgfplots vom 07.03.2014 und die Packagegröße ist 16284594 Byte. Ist das nicht die neuste Version 1.10? Kann bitte jemand meinen Code mal testen?

Danke im Voraus!

Grüße
unit

esdd
Forum-Meister
Forum-Meister
Beiträge: 2561
Registriert: So 7. Feb 2010, 16:36

Beitrag von esdd »

Das entspricht der aktuellen Version. Wenn ich die Zeile mit dem \usetikzlibrary{pgfplots.fillbetween} auskommentiere, erhalte ich mit deinem Code auch das erwartete Ergebnis ohne Fehlermeldungen.

Schau mal in deiner log Datei, ob die aktuelle pgfplots Version auch tatsächlich verwendet wird.

Gruß
Elke
Zuletzt geändert von esdd am Mi 12. Mär 2014, 13:52, insgesamt 1-mal geändert.

unit
Forum-Newbie
Forum-Newbie
Beiträge: 3
Registriert: Mi 12. Mär 2014, 10:07

Beitrag von unit »

Vielen Dank fürs Schauen!
esdd hat geschrieben:Schau mal in deiner log Datei, ob die aktuelle pgfplots Version auch tatsächlich verwendet wird.
Ich habe eben mal neu compiliert, in der Logdatei steht:

"Package: pgfplots 2013/10/03 v1.9 Data Visualization (1.9)"

Ich verstehe das nicht. Ich habe in der Zwischenzeit sogar versucht das Package manuell zu installieren: tds-Datei heruntergeladen, die Dateien in den MiKTeX-Ordner kopiert, dann "Refresh FNDB" und "Update Formats" durchgeführt. Selbst das updatet das Package nichts.

Was soll ich machen? Bringt eine Neuinstallation von MiKTeX was?

Gruß
unit

Antworten