1

The problem

I need a chart like this (this was made in Libreoffice + some gimp):

made with libreoffice and some gimp upon the exported image

Its caracteristics are bassically:

  1. Some stacked bars (if it is a stacked area, better) with its Y axis at the left.
  2. A line with its Y axis at the right.
  3. Kind of "sections" in the X axis (as can be seen in the picture). I don't know how are they called.
  4. Legend in both Y axis.

I need several of these charts with different values, so it would be nice to make them with the same code but different chartX.dat files.

If you could make a minimal example I would appreciate it a lot.

What I have

Currently I have my csv files like this:

   time,   Blue,    Red, Yellow,  Green,  Brown
      0,      8,     20,     18,     19,    401
      1,      7,     65,     13,     30,    914
      2,     10,     88,      0,      0,   1215

And other with the section cuts (always starting with 0 and finishing with the biggest number):

0
3
10
22
24

What I have seen

  • This graph and line chart, but it is not stacked and do not have the "sections".
  • Here a Stacked bar chart, but I am having troubles merging them.
  • Haven't seen those "sections"

Thanks for your time.

4
  • Do you try to compose your graph on basis of examples in mentioned link? Is there a problem only add those "section"?
    – Zarko
    Feb 4, 2016 at 16:57
  • I don't know how to put it all together, I have not idea how to make those "sections" nor how to include them in the data file
    – onlycparra
    Feb 4, 2016 at 17:00
  • Left section behind, you just draw graph without them. You have example, start with it with your data. Section will be relative easy add to it. On this way you will learn how to draw graphs.
    – Zarko
    Feb 4, 2016 at 17:07
  • Oh, I get it, sorry, it was drawn in Libreoffice, I will put it in the question :)
    – onlycparra
    Feb 4, 2016 at 17:28

3 Answers 3

1

First of all, I'm not implying in this answer that your plot has reached the point of being too complicated for tikz-pgf. This answer is inspired by many complex plotting questions that came before yours.

Once a plot starts getting complicated enough, you reach a point where it makes more sense to just program it from scratch using your favorite graphical programming language. For me, that language is usually asymptote. Asymptote produces vector graphics and uses Latex for typesetting. It can be embedded within your Latex file if that is your preference.

The problem with using a canned plotting routine is that you can spend excessive time trying to coerce that routine into your specific plot requirements. Often, even after spending lots of time, it is still necessary to sacrifice some of your requirements.

The following plot is created by a standalone asymptote script that is reading two external data files as listed below the plot. It is designed to look like your example plot above and your sample plot in this question. The script is 142 lines including some blank lines. Many of the lines are simple things that are common to many of my plotting scripts, such as drawing and labeling ticks. This method gets pretty quick after making many plots "from scratch". Best of all, no compromises are necessary.

enter image description here

texpreamble("\usepackage{charter}");

unitsize(1inch);

real wplot = 8.0;
real hplot = 5.0;

pair[] steps; // (start, end)
// Read steps.dat and fill steps array
{
    file fin = input("steps.dat").line();
    real[] values;
    while (!eof(fin)) { values.push((real) fin); }
    for (int i = 1; i < values.length; ++i)
    {
        steps.push((values[i-1], values[i]));
    }
}

struct Datum // a structure to hold the data associated with one time
{
    real time;
    real[] cores;
    real mem;
    void operator init(real time, real[] cores, real mem)
    {
        this.time = time;
        this.cores = cores;
        this.mem = mem;
    }
}
Datum[] data;
// Read data.dat and fill data array
{
    file fin = input("data.dat");
    while (true)
    {
        real time = fin;
        real[] cores = {fin, fin, fin, fin};
        real mem = fin;
        if (eof(fin)) { break; }
        data.push(Datum(time, cores, mem));
    }
}

real minTime = data[0].time;
real maxTime = data[data.length-1].time;
real timeToX(real time) { return (time - minTime) * wplot / (maxTime - minTime); }

real minCore = 0.0;
real maxCore = 400.0;
real coreToY(real core) { return (core - minCore) * hplot / (maxCore - minCore); }

real minMem = -400.0;
real maxMem = 7600.0;
real memToY(real mem) { return (mem - minMem) * hplot / (maxMem - minMem); }

// draw steps
{
    real stepDrop = 1.0;
    for (int i = 0; i < steps.length; ++i)
    {
        real xmin = timeToX(steps[i].x);
        real xmax = timeToX(steps[i].y);
        pen fillPen = i % 2 == 1 ? rgb(0.9,1.0,0.9) : white;
        filldraw(shift(xmin,-stepDrop)*
                scale(xmax-xmin,hplot+stepDrop)*unitsquare, fillPen, lightgray);
        label("\large Step "+string(i+1), ((xmax+xmin)/2,-stepDrop), N);
    }
}

real tickLength = 0.08;

// draw time ticks and labels
for (real time = minTime; time <= maxTime; time += 1.0)
{
    real x = timeToX(time);
    label(string(time), (x,0), 3*S, UnFill);
    draw(shift(x,0)*((0,0)--(0,-tickLength)), gray);
}

// draw core ticks and labels
for (real core = minCore; core <= maxCore; core += 50.0)
{
    real y = coreToY(core);
    label(string(core), (0,y), 3*W);
    draw(shift(0,y)*((wplot,0)--(-tickLength,0)), gray);
}

// draw mem ticks and labels
for (real mem = minMem; mem <= maxMem; mem += 1000.0)
{
    real y = memToY(mem);
    label(string(mem), (wplot,y), 3*E);
    draw(shift(wplot,y)*((0,0)--(tickLength,0)), gray);
}

path[] corePaths = array(4, nullpath);
// construct core paths
{
    for (int timeStep = 0; timeStep < data.length; ++timeStep)
    {
        real coreSum = 0.0;
        for (int coreIndex = 0; coreIndex < 4; ++coreIndex)
        {
            real thisCore = data[timeStep].cores[coreIndex];
            coreSum += thisCore;

            corePaths[coreIndex] = corePaths[coreIndex]--
                (timeToX(data[timeStep].time), coreToY(coreSum));
        }
    }
}

pen[] corePens = { blue, red, darkolive, black };
// plot core paths
{
    for (int coreIndex = 3; coreIndex > -1; --coreIndex)
    {
        filldraw(corePaths[coreIndex]--(wplot,0)--(0,0)--cycle,
                lightgray+0.5*corePens[coreIndex], 1+corePens[coreIndex]);
    }
}

path memPath = nullpath;
// construct mem path
{
    for (int timeStep = 0; timeStep < data.length; ++timeStep)
    {
        real thisMem = data[timeStep].mem;
        memPath = memPath--(timeToX(data[timeStep].time), memToY(thisMem));
    }
}

// plot mem path
draw(memPath, 3+green);

draw(scale(wplot,hplot)*unitsquare, 2+gray); // draw plot outline

label("\large Time", (wplot/2,-0.5));
label(rotate(90)*"\large CPU Usage", (-0.8,hplot/2));
label(rotate(90)*"\large Memory Usage", (wplot+0.8,hplot/2));

The contents of data.dat are...

0 7.847 19.51 18.389 18.943 400.90
1 6.863 64.706 12.871 30 913.50
2 10 88 0 0 1215.19
3 57.576 39 0 0 1691.61
4 0.99 99 0.99 0 1694.64
5 0 40.594 60 0 1698.15
6 0 96.939 3.03 0 1699.55
7 0 50.495 48.515 0 1700.09
8 0.99 53 47 0 1703.00
9 0 28.283 69 3 1696.77
10 31.313 0 0 67.677 1697.30
11 15 84 1.01 2.941 2252.78
12 0 15 14.141 71.717 2249.72
13 31 27 6.931 37 2249.00
14 2 13.725 60.606 28 2248.16
15 9 34.343 41 19 2248.31
16 32 41.414 25.743 0 2250.18
17 26 33.663 20.408 21 2249.89
18 23 13 40 25.253 2249.89
19 47.525 18.182 22 12.121 2249.60
20 34.694 25.253 22.772 16.832 2249.32
21 22 0.99 42.574 37.374 2249.01
22 12.871 24 12.121 56.436 2251.39
23 17.172 15.152 49.02 20.202 2252.57
24 27 5.051 32.653 36 2252.72

The contents of steps.dat are...

0
3
10
22
24
10
  • wow, this is awesome! Thanks a lot, it is exactly what I need, by the way, people like me that didn't know this language would like to see some documentation about asymptote, maybe it is a good idea to add it to your answer. Thanks again, James :)
    – onlycparra
    Feb 5, 2016 at 20:39
  • Hi @James, I have a problem with this solution, I can't make it work with a different set of data.
    – onlycparra
    Feb 12, 2016 at 21:35
  • @onlycparra: I think the most likely error is that the steps.dat or data.dat files are not formatted the same as in my examples. For example, a header line or a blank line might make it fail because I didn't include any code to detect and ignore those lines. If that doesn't help, then you should include an asymptote error message in your next comment.
    – James
    Feb 12, 2016 at 21:52
  • Thanks for replying, I placed the code in charts_2.asy and I compile it with asy -V -f pdf charts_2. With the original data, it runs well, but with the other set, I get nothing at all. The only difference I see in the sets is that sometimes the last value in steps.dat is a little higher than the last in data.dat.
    – onlycparra
    Feb 12, 2016 at 22:18
  • @onlycparra: I changed the last step to 25 and it still runs fine, so that shouldn't be an issue. You say you get nothing at all, by which I guess you mean no pdf file. Don't you get an error message within the command window though?
    – James
    Feb 13, 2016 at 0:49
4

I tried to create a first draft of a diagramm with your requirements. I used the packages pgfplots and tikz.

\begin{tikzpicture}
            \begin{axis}[ybar stacked, axis y line*=left] %first axis
            \addplot coordinates %blue
                    {(0,8)(1,7)(2,10)};
            \addplot coordinates %red
                    {(0,20) (1,65) (2,88)};

           %sections
           \draw (axis cs:0,0) grid (axis cs:2,100);
            \draw[fill, blue, opacity=0.4] (axis cs:0.5,0)--(axis cs:0.5,100)--(axis cs:1.5,100)--(axis cs:1.5,0);    
           \end{axis}

            \begin{axis}[   axis y line*=right,  
                            axis x line = none] %second axis, function 

                    \addplot[]{x+5};

            \end{axis}  

            \end{tikzpicture}
4
  • +1, please add preamble to the your code, that the OP can copy it and compile. I test your MWE in may "testbed" with standalone documentclass. It works fine.
    – Zarko
    Feb 4, 2016 at 23:14
  • Thank you Carina, I am figuring out how to create those labels (As I show in my image "step 1", "step 2"... etc.). Some Idea?
    – onlycparra
    Feb 5, 2016 at 2:30
  • I have made some work thanks to your advice, It is nice but I am still refining it, If you want, please check it out here
    – onlycparra
    Feb 5, 2016 at 6:56
  • I would try making the labels with \node at (x,y) {nodetext}; and search for the right position, I am sure there is a more elegant way, but I don't know any. Axes coordinates, don't work outside the axes.
    – Carina
    Feb 15, 2016 at 15:32
3

This is a bit modified solution from the follow-up question's solution. I dedicded to draw the "steps" on top of the graphs, because this space is unused. Then also the vertical lines don't cross the xticklabels of the first axis environment. And I think it looks odd to have a full fill but an interrupted drawing.

Please have a look at the comments of the code for details of how the solution works.

% works with PGFPlots v1.13 and TikZ v3.0.1a
\documentclass[border=2mm,many]{standalone}
\usepackage{pgfplots}
\usepackage{pgfplotstable}
    \pgfplotsset{compat=1.11}

\usepackage{filecontents}
    \begin{filecontents}{data.txt}
        Time core1 core2 core3 core4 mem
        0   7.847  19.51   18.389  18.943    400.90
        1   6.863  64.706  12.871  30        913.50
        2  10      88       0       0       1215.19
        3  57.576  39       0       0       1691.61
        4   0.99   99       0.99    0       1694.64
        5   0      40.594  60       0       1698.15
        6   0      96.939   3.03    0       1699.55
        7   0      50.495  48.515   0       1700.09
        8   0.99   53      47       0       1703.00
        9   0      28.283  69       3       1696.77
        10 31.313   0       0      67.677   1697.30
        11 15      84       1.01    2.941   2252.78
        12   0     15      14.141  71.717   2249.72
        13  31     27       6.931  37       2249.00
        14  2      13.725  60.606  28       2248.16
        15  9      34.343  41      19       2248.31
        16 32      41.414  25.743   0       2250.18
        17 26      33.663  20.408  21       2249.89
        18 23      13      40      25.253   2249.89
        19 47.525  18.182  22      12.121   2249.60
        20 34.694  25.253  22.772  16.832   2249.32
        21 22       0.99   42.574  37.374   2249.01
        22 12.871  24      12.121  56.436   2251.39
        23 17.172  15.152  49.02   20.202   2252.57
        24 27       5.051  32.653  36       2252.72
    \end{filecontents}

    \begin{filecontents}{data2.txt}
        0
        3
        10
        22
        24
    \end{filecontents}

\begin{document}
    \begin{tikzpicture}

        % define `xmax' value
        % (it has to be a command because it is later needed outside of an
        %  axis environment to filter the `steps' elements, which are greater
        %  than `xmax')
        \def\xmax{24}

        % define color for the vertical lines for the steps
        \colorlet{step color}{black!60}


        % define here what both axis environments have in common
        % so you don't have to repeat this stuff at every axis
        \pgfplotsset{
            every axis/.append style={
                enlargelimits=false,
                width=15cm,
                height=8cm,
                xmin=0,
                xmax=\xmax,
                axis on top,
            },
        }

            %%% collect all time stamps of the steps in `\allX'
            %%% it is later used in the axis environment to draw the lines
            %%% below the axis lines
            % store table for the steps
            \pgfplotstableread[header=true]{data2.txt}{\data}
            % store number of rows
            \pgfplotstablegetrowsof{\data}
                \pgfmathsetmacro{\rows}{\pgfplotsretval-1}
            % store first element to `\allX'
            \pgfplotstablegetelem{0}{[index] 0}\of\data
                \pgfmathsetmacro{\first}{\pgfplotsretval}
            \def\allX{\first}
            % cycle through the rest of the list and append the time to
            % `\allX' if the value is smaller than `\xmax'
            \pgfplotsforeachungrouped \i in {1,...,\rows} {
                \pgfplotstablegetelem{\i}{[index] 0}\of\data
                \pgfmathparse{(\pgfplotsretval<\xmax) ? 1 : 0}
                \ifdim \pgfmathresult pt>0pt
                    \edef\allX{\allX,\pgfplotsretval}
                \fi
            }

        \begin{axis}[
            area style,
            stack plots=y,
            xlabel={Time},
            ylabel={CPU usage},
            ymin=0,
            ymax=150,
            ytick distance=25,  % <-- to match ticks on both axis
        ]

            \foreach \i in {1,...,4}{
                \addplot table [x=Time,y=core\i]{data.txt} \closedcycle;
            }

            % use `ybar interval' plot to fake the vertical fills
            % (for that the `stack plots' has to be turned of to avoid
            %  an error message. Also this has to be plotted _after_ the
            %  `stack plots', because the plot sequence is reversed for
            %  `stack plots' --> last `\addplot' is drawn first)
            \addplot [
                stack plots=false,
                draw=none,
                fill=green!10,
                ybar interval,
            ]
                table [
                    x index=0,
                    % because only every second step should be filled
                    % switch the height of the bar between `ymax' and
                    % `ymin'. (To fill it the other way round, just
                    % replace `ymin' with `ymax' and vice versa)
                    y expr={ifthenelse(mod(\coordindex,2) == 0,
                        \pgfkeysvalueof{/pgfplots/ymax},
                        \pgfkeysvalueof{/pgfplots/ymin})
                    },
                ]
                    {data2.txt};

        \end{axis}

        \begin{axis}[
            no markers,
            %
            %%% draw step labels
            % therefore use the data of the first `\addplot'
            xtick=data,
            % they should be drawn in the middle of two values
            x tick label as interval,
            % define how the label should look like
            xticklabel={
                % because indexing starts at 0 --> add 1
                \pgfmathparse{\ticknum + 1}
                Step \pgfmathprintnumber{\pgfmathresult}
            },
            %
            ymin=0,
            ymax=3000,
            ylabel=memory usage,
            % draw "step" labels on top of graph
            xticklabel pos=upper,
            yticklabel pos=right,% <---
            clip=false,% <---
            % in case steps are larger than `xmax' -->  force it to be `xmax'
            % (here you see how to extract the `xmin' and `xmax' values
            %  when you are _inside_ of an axis environment)
            restrict x to domain*=
                \pgfkeysvalueof{/pgfplots/xmin}:\pgfkeysvalueof{/pgfplots/xmax},
        ]

            % use `ybar interval' plot to fake some vertical lines
            % this also enables the easy printing of the `xticklabels'
            \addplot [
                draw=step color,
                ybar interval,
            ]
                table [
                    x index=0,
                    y expr={ifthenelse(mod(\coordindex,2) == 0,
                        \pgfkeysvalueof{/pgfplots/ymax},
                        \pgfkeysvalueof{/pgfplots/ymin})
                    },
                ]
                    {data2.txt};

            % now draw the other lines regarding to the second y axis
            \addplot [very thick,draw=green] table [x=Time,y=mem] {data.txt};

        \end{axis}
    \end{tikzpicture}
\end{document}

image showing the result of above code

10
  • This is a very good answer, thanks, but due the early response time, I have already chosen James answer. Sorry and thanks for the time inversed in compose this complete answer. In the future, surely a lot of users will like this one instead of the previous
    – onlycparra
    Feb 8, 2016 at 15:09
  • Thank you, but I don't get your point. If you think that my answer fits your needs better than Jame's answer, you are free to change the accepted flag, as you can read here. Feb 8, 2016 at 17:06
  • I said your answer is "very good", not "better than". Both solves the problem, both are good, but James was just quicker than you, that's it. If depends on me, I would mark both as accepted answers. My final comment (in the future, surely...) is related with the fact that James uses Asymptote and it may be a little more complicated for some people. I thank you anyway and sorry if I disappoint you
    – onlycparra
    Feb 8, 2016 at 22:37
  • @onlycparra, I am perfectly fine with that. I don't know asymptote, but the result looks quite nice, yes. So I think it is just a matter of taste which solution is taken. So the goal of this site is matched: Provide not only the best, but as much as possible answers. Feb 9, 2016 at 6:36
  • Exactly, when I browse for a question, I commonly build my own answer based on the marked one and the others too, sometimes it is more useful for me other than the accepted one...
    – onlycparra
    Feb 9, 2016 at 6:41

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .