Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 197 additions & 0 deletions lib/plotrb/simple.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
#--
# simple.rb: Shortcuts for making some simple plots.
# Copyright (c) 2013 Colin J. Fuller and the Ruby Science Foundation
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# - Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# - Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the
# distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
# WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#++

require 'plotrb'

module Plotrb
module Simple

SCATTER_DATA_NAME = 'scatter'
SCATTER_X_SCALE_NAME = 'scatter_x'
SCATTER_Y_SCALE_NAME = 'scatter_y'

#
# Data name used by scatter plot
#
def self.scatter_data_name
SCATTER_DATA_NAME
end

#
# Scale name used by scatter plot for x axis
#
def self.scatter_x_scale_name
SCATTER_X_SCALE_NAME
end

#
# Scale name used by scatter plot for y axis
#
def self.scatter_y_scale_name
SCATTER_Y_SCALE_NAME
end

#
# Generate a simple 2d scatter plot.
#
# @param [NMatrix, Array] x the x datapoints; if a single row, will be used
# for all y dataseries, if multiple rows, each row of x will be used for
# the corresponding row of y
# @param [NMatrix, Array] y the y datapoints. Can be a single dimensional array,
# or 2D with multiple series in rows; column dimension should match the
# number of elements in x.
# @param [String, Array<String>] symbol the type of symbol to be used to
# plot the points. Can be any symbol Vega understands: circle, square,
# cross, diamond, triangle-up, triangle-down. If a single String is
# provided, this will be used for all the points. If an Array of Strings
# is provided, each symbol in the array will be used for the corresponding
# data series (row) in y. Default: 'circle'
# @param [String, Array<String>] color the color to be used to plot the
# points. If a single String is provided, this will be used for all the
# points. If an Array of Strings is provided, each color in the array will
# be used for the corresponding data series (row) in y. Default: 'blue'
# @param [Numeric] markersize the size of the marker in pixels. Default: 20
# @param [Numeric] width the visualization width in pixels. Default: 640
# @param [Numeric] height the visualization height in pixels. Default: 480
# @param [Array, String] domain the domain for the plot (limits on the
# x-axis). This can be a 2-element array of bounds or any other object
# that Plotrb::Scale::from understands. Default: scale to x data
# @param [Array, String] range the range for the plot (limits on the
# y-axis). This can be a 2-element array of bounds or any other object
# that Plotrb::Scale::from understands. Default: scale to first row of
# y.
#
# @return [Plotrb::Visualization] A visualization object. (This can be
# written to a json string for Vega with #generate_spec.)
#
# method signature for ruby 2.0 kwargs:
# def scatter(x, y, symbol: 'circle', color: 'blue', markersize: 20,
# width: 640, height: 480, domain: nil, range: nil)
def self.scatter(x, y, kwargs={})
kwargs = {symbol: 'circle', color: 'blue', markersize: 20, width: 640,
height: 480, domain: nil, range: nil}.merge(kwargs)
symbol = kwargs[:symbol]
color = kwargs[:color]
markersize = kwargs[:markersize]
width = kwargs[:width]
height = kwargs[:height]
domain = kwargs[:domain]
range = kwargs[:range]

datapoints = []
n_sets = 1
x_n_sets = 1
x_size = x.size

if x.respond_to?(:shape) and x.shape.length > 1 then # x is 2D NMatrix
x_n_sets = x.shape[0]
x_size = x.shape[1]
elsif x.instance_of? Array and x[0].instance_of? Array then # x is nested Array
x_n_sets = x.size
x_size = x[0].size
end

if y.respond_to?(:shape) and y.shape.length > 1 then # y is 2D NMatrix
n_sets = y.shape[0]
elsif y.instance_of? Array and y[0].instance_of? Array then # y is nested array
n_sets = y.size
end

x_size.times do |i|
dp = {}
n_sets.times do |j|

xj = j.modulo(x_n_sets)
if x.respond_to?(:shape) and x.shape.length > 1 then
dp["x#{xj}".to_sym] = x[xj, i]
elsif x.instance_of? Array and x[0].instance_of? Array then
dp["x#{xj}".to_sym] = x[xj][i]
else
dp["x#{xj}".to_sym] = x[i]
end

indices = [i]
if y.respond_to?(:shape) and y.shape.length > 1 then
indices = [j,i]
end
if y.instance_of? Array and y[0].instance_of? Array then
dp["y#{j}".to_sym] = y[j][*indices]
else
dp["y#{j}".to_sym] = y[*indices]
end
end

datapoints << dp
end

Plotrb::Kernel.data.delete_if { |d| d.name == scatter_data_name }
dataset= Plotrb::Data.new.name(scatter_data_name)
dataset.values(datapoints)

domain_in = "#{scatter_data_name}.x0"
if domain then
domain_in = domain
end
range_in = "#{scatter_data_name}.y0"
if range then
range_in = range
end

Plotrb::Kernel.scales.delete_if { |d| d.name == scatter_x_scale_name or d.name == scatter_y_scale_name }

xs = linear_scale.name(scatter_x_scale_name).from(domain_in).to_width
ys = linear_scale.name(scatter_y_scale_name).from(range_in).to_height

marks = []
n_sets.times do |j|
marks << symbol_mark.from(dataset) do
c_j = color.instance_of?(Array) ? color[j] : color
s_j = symbol.instance_of?(Array) ? symbol[j] : symbol
x_j = j.modulo(x_n_sets)
enter do
x_start { scale(xs).from("x#{x_j}") }
y_start { scale(ys).from("y#{j}") }
size markersize
shape s_j
fill c_j
end
end
end

visualization.width(width).height(height) do
data dataset
scales xs, ys
marks marks
axes x_axis.scale(xs), y_axis.scale(ys)
end
end
end
end

61 changes: 61 additions & 0 deletions spec/plotrb/simple_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#--
# simple_spec.rb: specs for simple plot shortcuts
# Copyright (c) 2013 Colin J. Fuller and the Ruby Science Foundation
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# - Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# - Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the
# distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
# WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#++

require 'spec_helper'
require 'plotrb/simple'

describe Plotrb::Simple do

context "scatter plots" do
before :each do
@xdata = [0,1,2,3,4,5]
@ydata = [[0,1,4,9,16,25],[0,1,8,27,64,125]]
end

it "should correctly make a scatter plot with supplied options" do
Plotrb::Simple.scatter(@xdata, @ydata, markersize: 40, domain: [0,10], range: [0,125], color: ['red', 'blue'], width: 1000, height: 1000, symbol: ['triangle-up', 'triangle-down']).generate_spec.should eq '{"width":1000,"height":1000,"data":[{"name":"scatter","values":[{"x0":0,"y0":0,"y1":0},{"x0":1,"y0":1,"y1":1},{"x0":2,"y0":4,"y1":8},{"x0":3,"y0":9,"y1":27},{"x0":4,"y0":16,"y1":64},{"x0":5,"y0":25,"y1":125}]}],"scales":[{"name":"scatter_x","type":"linear","domain":[0,10],"range":"width"},{"name":"scatter_y","type":"linear","domain":[0,125],"range":"height"}],"marks":[{"type":"symbol","from":{"data":"scatter"},"properties":{"enter":{"size":{"value":40},"shape":{"value":"triangle-up"},"x":{"field":"data.x0","scale":"scatter_x"},"y":{"field":"data.y0","scale":"scatter_y"},"fill":{"value":"red"}}}},{"type":"symbol","from":{"data":"scatter"},"properties":{"enter":{"size":{"value":40},"shape":{"value":"triangle-down"},"x":{"field":"data.x0","scale":"scatter_x"},"y":{"field":"data.y1","scale":"scatter_y"},"fill":{"value":"blue"}}}}],"axes":[{"type":"x","scale":"scatter_x"},{"type":"y","scale":"scatter_y"}]}'
end

it "should correctly make a scatter plot with default options and multiple data series" do
Plotrb::Simple.scatter(@xdata, @ydata).generate_spec.should eq '{"width":640,"height":480,"data":[{"name":"scatter","values":[{"x0":0,"y0":0,"y1":0},{"x0":1,"y0":1,"y1":1},{"x0":2,"y0":4,"y1":8},{"x0":3,"y0":9,"y1":27},{"x0":4,"y0":16,"y1":64},{"x0":5,"y0":25,"y1":125}]}],"scales":[{"name":"scatter_x","type":"linear","domain":{"data":"scatter","field":"data.x0"},"range":"width"},{"name":"scatter_y","type":"linear","domain":{"data":"scatter","field":"data.y0"},"range":"height"}],"marks":[{"type":"symbol","from":{"data":"scatter"},"properties":{"enter":{"size":{"value":20},"shape":{"value":"circle"},"x":{"field":"data.x0","scale":"scatter_x"},"y":{"field":"data.y0","scale":"scatter_y"},"fill":{"value":"blue"}}}},{"type":"symbol","from":{"data":"scatter"},"properties":{"enter":{"size":{"value":20},"shape":{"value":"circle"},"x":{"field":"data.x0","scale":"scatter_x"},"y":{"field":"data.y1","scale":"scatter_y"},"fill":{"value":"blue"}}}}],"axes":[{"type":"x","scale":"scatter_x"},{"type":"y","scale":"scatter_y"}]}'
end

it "should correctly make a scatter plot with default options and a single data series" do
Plotrb::Simple.scatter(@xdata, [0,1,4,9,16,25]).generate_spec.should eq '{"width":640,"height":480,"data":[{"name":"scatter","values":[{"x0":0,"y0":0},{"x0":1,"y0":1},{"x0":2,"y0":4},{"x0":3,"y0":9},{"x0":4,"y0":16},{"x0":5,"y0":25}]}],"scales":[{"name":"scatter_x","type":"linear","domain":{"data":"scatter","field":"data.x0"},"range":"width"},{"name":"scatter_y","type":"linear","domain":{"data":"scatter","field":"data.y0"},"range":"height"}],"marks":[{"type":"symbol","from":{"data":"scatter"},"properties":{"enter":{"size":{"value":20},"shape":{"value":"circle"},"x":{"field":"data.x0","scale":"scatter_x"},"y":{"field":"data.y0","scale":"scatter_y"},"fill":{"value":"blue"}}}}],"axes":[{"type":"x","scale":"scatter_x"},{"type":"y","scale":"scatter_y"}]}'
end

it "should correctly make a scatter plot with default options and multiple x and y data series" do
@xdata = [[0,1,2,3,4,16], [10,11,12,13,14,15]]
Plotrb::Simple.scatter(@xdata, @ydata).generate_spec.should eq '{"width":640,"height":480,"data":[{"name":"scatter","values":[{"x0":0,"y0":0,"x1":10,"y1":0},{"x0":1,"y0":1,"x1":11,"y1":1},{"x0":2,"y0":4,"x1":12,"y1":8},{"x0":3,"y0":9,"x1":13,"y1":27},{"x0":4,"y0":16,"x1":14,"y1":64},{"x0":16,"y0":25,"x1":15,"y1":125}]}],"scales":[{"name":"scatter_x","type":"linear","domain":{"data":"scatter","field":"data.x0"},"range":"width"},{"name":"scatter_y","type":"linear","domain":{"data":"scatter","field":"data.y0"},"range":"height"}],"marks":[{"type":"symbol","from":{"data":"scatter"},"properties":{"enter":{"size":{"value":20},"shape":{"value":"circle"},"x":{"field":"data.x0","scale":"scatter_x"},"y":{"field":"data.y0","scale":"scatter_y"},"fill":{"value":"blue"}}}},{"type":"symbol","from":{"data":"scatter"},"properties":{"enter":{"size":{"value":20},"shape":{"value":"circle"},"x":{"field":"data.x1","scale":"scatter_x"},"y":{"field":"data.y1","scale":"scatter_y"},"fill":{"value":"blue"}}}}],"axes":[{"type":"x","scale":"scatter_x"},{"type":"y","scale":"scatter_y"}]}'
end
end
end