-
Notifications
You must be signed in to change notification settings - Fork 19
Plot Widget
Drawing a graph or a chart or a plot? Shoes 3.3.2 (beta r2649 or better) has a plot widget. It is not a replacement for programs like gnuplot, LibreOffice, OpenOffice, or other professional quality programs. Shoes has a limited set of options and they don't alway work as you might expect. You can always export your data to csv and use a better program to display the data.
Now that you know what to do when disappointed. Let's have some fun!
Assume we have an array of numbers and we want to plot them.
Shoes.app width: 620, height: 500 do
@values1 = [24, 22, 10, 13, 20, 8, 22]
@grf = plot 600, 400, title: "My Graph", caption: "my caption could be long",
font: "Helvetica"
@grf.add values: @values1, name: "foobar", minv: 6, maxv: 26
end
That is about as minimal as we can get. It looks like this:
You'll notice that there are two parts. Creating the plot widget and saving it as @grf and adding the array to that @grf. You can create the widget with out adding a data series to display so in truth, you could use @grf = plot 600, 400, {}
but that's boring and the fun is in the {options}. You can graph multiple data series (up to 6 for some chart types). The {options} for add are important and not so obvious. I call them "data series" because there is more information needed than just a Ruby array of values.
The min: and max: options could be calculated for you but aren't because it gives you some control on how the graph looks. So much control, that you shouldn't use defaults, so are there are none.
What if you wanted your own x-axis labels instead of the generated 1,2,3...?
Shoes.app width: 620, height: 500 do
@values1 = [24, 22, 10, 13, 20, 8, 22]
@x_axis1 = ['a','b','c','d','e','f', 'g']
@grf = plot 600, 400, title:"My Graph", caption: "my caption could be long",
font: "Helvetica"
@grf.add values: @values1, labels: @x_axis1,
name: "foobar", min: 6, max: 26
end
It's a major mistake if you don't have enough strings in the x observations (labels: array) as you do in the values: array. Since labels[] are strings they could be anything. Perhaps a Date or Time string? - up to you. You should have the same number of labels[] and values[]. Shoes will auto-size the data points (observations) to the range given. I could have used min: @values1.min, max: @values1.max
but specifying my own range is a very useful thing if your plot has two data series attached with very different scales.
Note:, don't be tempted to use Ruby's min and max functions to compute the chart options. They don't like nil values which some chart types can handle.
Inside Shoes, each plot.add adds to an array so the first data series is 0 and the second is 1. Give the data series a name: "string". Mandatory. It displays in the legend part of the box.
The first data series added to the plot also controls what is displayed in the x-axis (there is only one of those) and will control the number of ticks marks and labels drawn on the left side and right side y-axis. Ticks? I don't see any ticks? Add auto_grid: true
to the plot creation.
@grf = plot 600, 400, title: "My Graph", caption: "my caption could be long",
font: "Helvetica", auto_grid: true
Let's use many of the options for plot and add options with a program that creates two data series, one of which has a missing value (it's nil) and an label is nil too.
See gr1.rb and comment/uncomment the settings for which set of values an labels you'd like to see.
That example also shows some more options when creating a plot.
chart: "timeseries",
default: "skip", click: proc {|btn, l, t| puts "click on #{@grf.near_x(l)}" }
There are several chart types - the default is "line". "Column", Scatter" and "Pie". "timeseries" is like "line" on steroids. Not all options apply to all chart types and in this case only line and timeseries allow for a click: and default: only works on some chart types.
From the gr1.rb example you can see a new option when adding a dataseries to the plot,
desc: "foobar Yy"
That (desc:) is displayed in the legend and colored to match the data series. name: is used if desc: isn't given. name: is mandatory, desc: is optional.
There are two other options for the plot widget creation. Shoes doesn't know the pixel width of the 'typical' label string. By default it's somewhere around 6 characters but you do get situations where the auto_sizing needs help (depends on your data and strings, the size of the widget....). So { x_ticks: 4, y_ticks: 20} would only draw four vertical lines on the grid and 20 horizontal lines, for example. This also introduces it's own visual artifacts so you'll need to play with what works best for you and of course it only applies to plot/chart types that accept them.
Wait, there's more options you can use when adding a data series. If you don't like the default colors, you can specify your own using the predefined names in Shoes (Manual->Shoes->Color) when you add a data series to the plot with the color: "shoes_color_string" option.
You can also set a wider line width for a data series with { strokewidth: small-int }. Anything less that one will be set to one, otherwise it wouldn't be visible.
You might notice in the picture there is some sort of dot drawn at each data point in the picture above. That's option { points: boolean }. It only looks like a circle - it's really a wee small '+' or box. The points , even if specified will only show up if Shoes thinks it has enough space for the given number of data points (not that many - if width can handle 1/10 of the data - you can get points in a line or timeseries). A data series with lots of observations (values) will turn off the points display. It just makes it slightly easier to visually find the point and figure out the data value.
== Timeseries vs Line Charts
Clever people would notice that if they set_first and and set_last that would zoom or unzoom or scroll left or right and if you get too zoomy you'll get the nubs. But only if you ask for them. Design goal?
[Sep-02-2016]
methods set_first and set_last change the display of data points. It does not change the size of values and xobs arrays. They change the display of every data series on the plot/graph. You use then to zoom in and scroll left or right. Expect a zoom method soon that sets both start and last.
redraw_to(index) is working, so lets mention why it exists and how to use it. It exists to append new data to the currently drawn data series - perhaps you've collected a data value in real time from a sensor or some web app. IT IS VERY IMPORTANT that you must append the data to the values array and [create] append and string for the xobs: array for all displayed data series BEFORE calling redraw_to.
Simply, redraw_to(index) sets the num_obs: of the plot and it also sets the plot's visual end_point (display) to the new position. If you're managing zooming or scrolling, you need to know that.
[Sep-04-2016]
Nearly done. I've added a plot.zoom(first, last) method which is the preferred method for zooming, unzooming, or horizontal scrolling around. You do have to manage the first and last indexes yourself. See this code (grtest.rb) with deals with a csv file with 1000's of data points and date. Performance is quite good.
require 'csv'
def load_test (filename, vals, obvs)
hdr_name = ''
CSV.foreach(filename, 'r') do |row|
hdr_name = row[0]
dts = row[1].to_str
#y = dts[0..3].to_i
#m = dts[4..5].to_i
#d = dts[6..7].to_i
#dt = DateTime.new(y,m,d,16,0,0,'-4')
v = row[2].to_f
obvs << "#{dts[2..3]}-#{dts[4..5]}-#{dts[6..7]}"
vals << v
end
return hdr_name
end
Shoes.app width: 620, height: 610 do
@vals = []
@obvs = []
@name = ''
zooming = false;
zoom_beg = 0
zoom_end = 0;
stack do
flow do
button "quit" do Shoes.quit end
button "load csv..." do
#short_name = load_test "/home/ccoupe/Projects/JModel-1.4/ruby/tstest.csv", @vals, @obvs
filename = ask_open_file
if filename
short_name = load_test filename, @vals, @obvs
para "loaded #{@vals.size}"
@grf.add num_obs: @vals.size, values: @vals, maxv: @vals.max * 1.01,
minv: @vals.min, name: short_name, xobs: @obvs, nubs: :true
zoom_end = @vals.size
end
end
button "in %25" do
range = zoom_end - zoom_beg
zoom_beg = (zoom_beg + (range * 0.125)).to_i
zoom_end = (zoom_end - (range * 0.125)).to_i
@grf.zoom zoom_beg, zoom_end
end
button "out %25" do
range = zoom_end - zoom_beg
zoom_beg = (zoom_beg - (range * 0.125)).to_i
zoom_end = (zoom_end + (range * 0.125)).to_i
@grf.zoom zoom_beg, zoom_end
end
button "reset" do
zoom_beg = 0
zoom_end = @vals.size
@grf.zoom zoom_beg, zoom_end
end
end
@grf = plot 600, 400, title: "Test - ^SPX ", caption: "1993 Jan 4 to May 25",
x_ticks: 8, y_ticks: 10, auto_grid: true
end
end
In this example I want to zoom in (25%) by upping the start index 12.5% and reducing the end index by 12.5%. zoom out just reverses that. NOTE: 12.5% is going to be result in some rounding fun depending on the length of the data. You'll learn to ignore that because it's just not worth your effort. Then again, it will be your code and your data so go for it. The zoom method does have one bounds checks - you can't zoom any smaller than 3 data points
One other recent enhancement is the plot.save_as method
button "save as" do
file = ask_save_file
@grf.save_as file if file
end
```
Allows you to save the plot (aka) graph as a png, pdf, ps or svg. Just make sure the filename extension is one of those four lower case, strings following the last `.` - it does parse the extension. The vector formats like .pdf are a lot of fun because you can stretch them or shrink them without pixel jaggies. Impressive. There might be a performance or file size issue with a 6 data series of 5000 data points each drawn on the screen. They draw just fine, but I haven't tried saving that to a vector fix because I can't fix whatever might be wrong with the file.
**[Sep 17, 2016]**
Lots of options have been added since the last update!!
You can fill the background with a Shoes color name. You can specify the color name for a data series. While the
default colors can be used, you probably want one from the Shoes pallet.
In the option hash, you can specify `chart: "column"` (default is `chart: "line"`). Here's two plot widgets
[source: gr3.rb](https://github.com/Shoes3/shoes3/blob/master/Tests/plot/gr3.rb):
![bar-graph](https://cloud.githubusercontent.com/assets/222691/18612528/827e11b0-7d19-11e6-9609-f041c99b1931.png)
Looking at the code, it draws the same data into two different plots widgets, one line chart and one column chart.
If you're paying attention, you'll notice there is no boundary box drawn around the column chart (aka plot). It's an option `{boundary_box: boolean}`, default is `:true".
A note or two about column graphs: It would be _your mistake_ to cram too many bars in a small space. strokewidth: is how you specify the width of column. Refer to that Shoes code link. If a column value matches the given minimum value, there is no column to draw so set your min and max by hand. Just a friendly warning if you think it's not displaying a value. Been there, done that.
[gr2.rb](https://github.com/Shoes3/shoes3/blob/master/Tests/plot/gr2.rb) shows how Shoes line chart can handle missing data points and missing observation labels. **Top Tip:** Fix your data and don't depend on Shoes drawing what you think it should do if you data is wrong.
**[Sep-19-2016]**
The nubs: hash argument can now take a string argument as well as a :true, :fail", or nil. The string argument is one of "dot", "circle", "box", or "rect". Dot and box are filled with series color and Circle and Box are hollow so the background shows inside. The size of of the dot/circle/box/rect is related to or controlled by the strokewidth: setting and they only become visible if Shoes thinks the chart is "not too busy".
**[Sep-23-2016]**
Many changes.
* added chart: "scatter"
* added chart: "timeseries"
For now, timeseries is just a type of line chart, but click and zooming only work on timeseries charts and are disabled for line charts and all other chart types. `redraw_to` only works on line and timeseries charts. _It's a good thing!_ and `redraw_to` for line charts is unlikely to survive the review.
Scatter charts are cumbersome and come with some odd rules and behavior in Shoes.
![almost-good-scatter](https://cloud.githubusercontent.com/assets/222691/18806307/21df17ec-81e6-11e6-908a-16c79f199b08.png)
The first rule is that it takes two @grf.add {series} - no more and no less than two. It ignores the xobs: of both data series. The first series added is assumed to be the x (horizontal) value and the second series added is the y (vertical) values. It's what you see in Excel or OpenOffice.
The second rule is that you really should specify the minv and maxv values for both series and set them wisely. Sadly, wisely doesn't have any rules-of-thumb.
You'll also notice that a scatter plot has a different legend - the x-axis label is drawn under the x axis and the y-axis (second series) is drawn vertically on the left. You might notice that compared to a line or bar chart there is a smaller graph area to allow that.
Pie charts also have to funny rules. First rule - only one data series can be added. Exactly one. And the x-axis labels (strings) are used to build the legend, which is display near the right-top. Auto-grid :true means to draw a box around the legend (not the chart space).
![pie-finished](https://cloud.githubusercontent.com/assets/222691/19026410/d3440eea-88e2-11e6-9e5a-4d338e4b2701.png)
One other thing you can do on a pie chart is specify `pie_percent: true` if you want the labels to be a percentage instead of the value.
**[Oct-07-2016]**
The option `num_obs:` is now ignored on @grf.add. We get the array length from Ruby.