motion-plot 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +18 -0
- data/.repl_history +0 -0
- data/Gemfile +9 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/Rakefile +22 -0
- data/app/app_delegate.rb +14 -0
- data/lib/motion-plot.rb +20 -0
- data/lib/motion-plot/chart/area_plot.rb +27 -0
- data/lib/motion-plot/chart/bar_plot.rb +74 -0
- data/lib/motion-plot/chart/base.rb +194 -0
- data/lib/motion-plot/chart/delegates/bar_delegate.rb +31 -0
- data/lib/motion-plot/chart/delegates/percent_bar_delegate.rb +60 -0
- data/lib/motion-plot/chart/delegates/stack_bar_delegate.rb +55 -0
- data/lib/motion-plot/chart/line_plot.rb +65 -0
- data/lib/motion-plot/chart/pie_plot.rb +128 -0
- data/lib/motion-plot/core_ext/ui_color.rb +5 -0
- data/lib/motion-plot/series/series.rb +44 -0
- data/lib/motion-plot/theme/theme.rb +22 -0
- data/lib/motion-plot/utilities/anchor_position.rb +27 -0
- data/lib/motion-plot/utilities/area_gradient.rb +18 -0
- data/lib/motion-plot/utilities/axis.rb +37 -0
- data/lib/motion-plot/utilities/data_label.rb +36 -0
- data/lib/motion-plot/utilities/legend.rb +45 -0
- data/lib/motion-plot/utilities/plot_area.rb +43 -0
- data/lib/motion-plot/utilities/plot_options.rb +16 -0
- data/lib/motion-plot/utilities/plot_symbol.rb +51 -0
- data/lib/motion-plot/utilities/style.rb +34 -0
- data/lib/motion-plot/utilities/text_style.rb +12 -0
- data/lib/motion-plot/utilities/title.rb +17 -0
- data/lib/motion-plot/version.rb +3 -0
- data/motion-plot.gemspec +23 -0
- metadata +127 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
module MotionPlot
|
2
|
+
class PercentBarDelegate
|
3
|
+
|
4
|
+
def initialize(source)
|
5
|
+
@delegated_to = source
|
6
|
+
@number_of_plots = @delegated_to.series.keys.size
|
7
|
+
end
|
8
|
+
|
9
|
+
def numberOfRecordsForPlot(plot)
|
10
|
+
@delegated_to.series[plot.identifier].data.size
|
11
|
+
end
|
12
|
+
|
13
|
+
def numberForPlot(plot, field:field_enum, recordIndex:index)
|
14
|
+
case field_enum
|
15
|
+
when CPTBarPlotFieldBarLocation
|
16
|
+
index
|
17
|
+
when CPTBarPlotFieldBarTip
|
18
|
+
plot_index = @delegated_to.series[plot.identifier].index
|
19
|
+
record_data = @delegated_to.series[plot.identifier].data[index]
|
20
|
+
bar_tip_value(plot_index, recordIndex:index, startValue:record_data)
|
21
|
+
when CPTBarPlotFieldBarBase
|
22
|
+
plot_index = @delegated_to.series[plot.identifier].index
|
23
|
+
bar_base_value(plot_index, recordIndex:index)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def barPlot(plot, barWasSelectedAtRecordIndex:index)
|
28
|
+
if(@delegated_to.data_label and @delegated_to.data_label.annotation)
|
29
|
+
@delegated_to.graph.plotAreaFrame.plotArea.removeAnnotation(@delegated_to.data_label.annotation)
|
30
|
+
@delegated_to.data_label.annotation = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
y_value = (@delegated_to.series[plot.identifier].data[index].round(2) / total_sum_at_index(index)) * 100
|
34
|
+
plot_index = @delegated_to.series[plot.identifier].index
|
35
|
+
y_pos = (0..plot_index).inject(0) {|base, i| base + (@delegated_to.data_hash[i][index] / total_sum_at_index(index)) * 100 }
|
36
|
+
|
37
|
+
@delegated_to.graph.plotAreaFrame.plotArea.addAnnotation(@delegated_to.data_label.annotation_for("#{y_value} %", atCoordinate: [index+CPTDecimalFloatValue(plot.barOffset), y_pos], plotSpace: @delegated_to.graph.defaultPlotSpace))
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
def bar_base_value(plot_index, recordIndex:index)
|
42
|
+
return 0 if(plot_index == 0)
|
43
|
+
|
44
|
+
(0..plot_index-1).inject(0) {|base, i| base + (@delegated_to.data_hash[i][index] / total_sum_at_index(index))*100 }
|
45
|
+
end
|
46
|
+
|
47
|
+
def bar_tip_value(plot_index, recordIndex:index, startValue:value)
|
48
|
+
return (value / total_sum_at_index(index))*100 if(plot_index == 0)
|
49
|
+
|
50
|
+
(0..plot_index).inject(0) {|base, i| base + (@delegated_to.data_hash[i][index] / total_sum_at_index(index))*100 }
|
51
|
+
end
|
52
|
+
|
53
|
+
def total_sum_at_index(index)
|
54
|
+
total ||= (0..@number_of_plots-1).inject(0) {|total, i| total + @delegated_to.data_hash[i][index]}
|
55
|
+
|
56
|
+
total
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module MotionPlot
|
2
|
+
class StackBarDelegate
|
3
|
+
|
4
|
+
# consign :series, :to => :delegated_to
|
5
|
+
|
6
|
+
def initialize(source)
|
7
|
+
@delegated_to = source
|
8
|
+
end
|
9
|
+
|
10
|
+
def numberOfRecordsForPlot(plot)
|
11
|
+
@delegated_to.series[plot.identifier].data.size
|
12
|
+
end
|
13
|
+
|
14
|
+
def numberForPlot(plot, field:field_enum, recordIndex:index)
|
15
|
+
case field_enum
|
16
|
+
when CPTBarPlotFieldBarLocation
|
17
|
+
index
|
18
|
+
when CPTBarPlotFieldBarTip
|
19
|
+
plot_index = @delegated_to.series[plot.identifier].index
|
20
|
+
record_data = @delegated_to.series[plot.identifier].data[index]
|
21
|
+
bar_tip_value(plot_index, recordIndex:index, startValue:record_data)
|
22
|
+
when CPTBarPlotFieldBarBase
|
23
|
+
plot_index = @delegated_to.series[plot.identifier].index
|
24
|
+
bar_base_value(plot_index, recordIndex:index)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def barPlot(plot, barWasSelectedAtRecordIndex:index)
|
29
|
+
if(@delegated_to.data_label and @delegated_to.data_label.annotation)
|
30
|
+
@delegated_to.graph.plotAreaFrame.plotArea.removeAnnotation(@delegated_to.data_label.annotation)
|
31
|
+
@delegated_to.data_label.annotation = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
y_value = @delegated_to.series[plot.identifier].data[index].round(2)
|
35
|
+
plot_index = @delegated_to.series[plot.identifier].index
|
36
|
+
y_pos = (0..plot_index).inject(0) {|base, i| base + @delegated_to.data_hash[i][index]}
|
37
|
+
|
38
|
+
@delegated_to.graph.plotAreaFrame.plotArea.addAnnotation(@delegated_to.data_label.annotation_for(y_value, atCoordinate: [index+CPTDecimalFloatValue(plot.barOffset), y_pos], plotSpace: @delegated_to.graph.defaultPlotSpace))
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
def bar_base_value(plot_index, recordIndex:index)
|
43
|
+
return 0 if(plot_index == 0)
|
44
|
+
|
45
|
+
(0..plot_index-1).inject(0) {|base, i| base + @delegated_to.data_hash[i][index] }
|
46
|
+
end
|
47
|
+
|
48
|
+
def bar_tip_value(plot_index, recordIndex:index, startValue:value)
|
49
|
+
return value if(plot_index == 0)
|
50
|
+
|
51
|
+
(0..plot_index).inject(0) {|base, i| base + @delegated_to.data_hash[i][index] }
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module MotionPlot
|
2
|
+
class Line < Base
|
3
|
+
|
4
|
+
attr_accessor :curve_inerpolation
|
5
|
+
|
6
|
+
def add_series
|
7
|
+
@series.keys.each_with_index do |name, index|
|
8
|
+
line = CPTScatterPlot.alloc.initWithFrame(CGRectNull)
|
9
|
+
line.identifier = name
|
10
|
+
|
11
|
+
line_style = line.dataLineStyle.mutableCopy
|
12
|
+
line_style.lineWidth = @series[name].width
|
13
|
+
|
14
|
+
line_style.lineColor = @series[name].color
|
15
|
+
|
16
|
+
line.dataLineStyle = line_style
|
17
|
+
line.dataSource = self
|
18
|
+
line.delegate = self
|
19
|
+
line.interpolation = CPTScatterPlotInterpolationCurved if(@curve_inerpolation)
|
20
|
+
|
21
|
+
add_plot_symbol(line, index) if(@plot_symbol)
|
22
|
+
|
23
|
+
@graph.addPlot(line)
|
24
|
+
@plots << line
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# This implementation of this method will put the line graph in a fix position so it won't be scrollable.
|
29
|
+
def plotSpace(space, willChangePlotRangeTo:new_range, forCoordinate:coordinate)
|
30
|
+
(coordinate == CPTCoordinateY) ? space.yRange : space.xRange
|
31
|
+
end
|
32
|
+
|
33
|
+
def scatterPlot(plot, plotSymbolWasSelectedAtRecordIndex:index)
|
34
|
+
if(@data_label and @data_label.annotation)
|
35
|
+
@graph.plotAreaFrame.plotArea.removeAnnotation(@data_label.annotation)
|
36
|
+
@data_label.annotation = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
y_value = @series[plot.identifier].data[index].round(2)
|
40
|
+
@graph.plotAreaFrame.plotArea.addAnnotation(@data_label.annotation_for(y_value, atCoordinate: [index, y_value], plotSpace: @graph.defaultPlotSpace))
|
41
|
+
end
|
42
|
+
|
43
|
+
def numberOfRecordsForPlot(plot)
|
44
|
+
@series[plot.identifier].data.size
|
45
|
+
end
|
46
|
+
|
47
|
+
def numberForPlot(plot, field:field_enum, recordIndex:index)
|
48
|
+
data = @series[plot.identifier].data
|
49
|
+
|
50
|
+
(field_enum == CPTScatterPlotFieldY) ? data[index] : index
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
def default_style
|
55
|
+
{
|
56
|
+
width: 2.0
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
def plot_type
|
61
|
+
"line"
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module MotionPlot
|
2
|
+
class Pie < Base
|
3
|
+
|
4
|
+
attr_accessor :selected_slice
|
5
|
+
|
6
|
+
def add_series
|
7
|
+
pie = CPTPieChart.alloc.init
|
8
|
+
pie.dataSource = self
|
9
|
+
pie.delegate = self
|
10
|
+
pie.pieRadius = pie_radius
|
11
|
+
pie.startAngle = Math::PI/4
|
12
|
+
pie.sliceDirection = CPTPieDirectionClockwise #CPTPieDirectionCounterClockwise
|
13
|
+
pie.identifier = plot_identifier
|
14
|
+
|
15
|
+
series_data.each_with_index {|obj, i| @selected_slice = i if(obj[:selected]) }
|
16
|
+
|
17
|
+
add_gradient(pie)
|
18
|
+
|
19
|
+
animate(pie)
|
20
|
+
@graph.addPlot(pie)
|
21
|
+
end
|
22
|
+
|
23
|
+
def default_padding
|
24
|
+
pie_series.style.paddings_for(@graph)
|
25
|
+
pie_series.style.plot_area.add_style(@graph.plotAreaFrame)
|
26
|
+
end
|
27
|
+
|
28
|
+
def numberOfRecordsForPlot(plot)
|
29
|
+
series_data.size
|
30
|
+
end
|
31
|
+
|
32
|
+
def numberForPlot(plot, field: fieldEnum, recordIndex: index)
|
33
|
+
fieldEnum == CPTPieChartFieldSliceWidth ? series_data[index][:y] : index
|
34
|
+
end
|
35
|
+
|
36
|
+
def legendTitleForPieChart(pie, recordIndex: index)
|
37
|
+
series_data[index][:name]
|
38
|
+
end
|
39
|
+
|
40
|
+
def sliceFillForPieChart(plot, recordIndex: index)
|
41
|
+
CPTFill.alloc.initWithColor Series::COLORS[index].to_color.to_cpt_color
|
42
|
+
end
|
43
|
+
|
44
|
+
def radialOffsetForPieChart(plot, recordIndex: index)
|
45
|
+
offset = 0.0
|
46
|
+
|
47
|
+
if(@selected_slice == index)
|
48
|
+
offset = pie_radius / 25.0
|
49
|
+
end
|
50
|
+
|
51
|
+
offset
|
52
|
+
end
|
53
|
+
|
54
|
+
def pieChart(plot, sliceWasSelectedAtRecordIndex: index)
|
55
|
+
@selected_slice = index
|
56
|
+
plot.reloadData
|
57
|
+
end
|
58
|
+
|
59
|
+
def dataLabelForPlot(plot, recordIndex: index)
|
60
|
+
@data_label.annotation_text_style(series_data[index][:y].round(2))
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
def plot_type
|
65
|
+
"pie"
|
66
|
+
end
|
67
|
+
|
68
|
+
def add_gradient(pie)
|
69
|
+
if(pie_series.style.gradient)
|
70
|
+
overlay_gradient = CPTGradient.alloc.init
|
71
|
+
overlay_gradient.gradientType = CPTGradientTypeRadial
|
72
|
+
|
73
|
+
overlay_gradient = overlay_gradient.addColorStop(CPTColor.blackColor.colorWithAlphaComponent(0.0), atPosition:0.0)
|
74
|
+
overlay_gradient = overlay_gradient.addColorStop(CPTColor.blackColor.colorWithAlphaComponent(0.3), atPosition:0.9)
|
75
|
+
overlay_gradient = overlay_gradient.addColorStop(CPTColor.blackColor.colorWithAlphaComponent(0.7), atPosition:1.0)
|
76
|
+
pie.overlayFill = overlay_gradient
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def pie_radius
|
81
|
+
[
|
82
|
+
0.8 * (@layer_hosting_view.frame.size.height - 2 * @graph.paddingLeft) / 2.0,
|
83
|
+
0.8 * (@layer_hosting_view.frame.size.width - 2 * @graph.paddingTop) / 2.0
|
84
|
+
].min
|
85
|
+
end
|
86
|
+
|
87
|
+
def plot_identifier
|
88
|
+
@series.keys.select{|k| @series[k].type == plot_type}.first
|
89
|
+
end
|
90
|
+
|
91
|
+
def series_data
|
92
|
+
pie_series.data
|
93
|
+
end
|
94
|
+
|
95
|
+
def pie_series
|
96
|
+
@series[plot_identifier]
|
97
|
+
end
|
98
|
+
|
99
|
+
def animate(plot)
|
100
|
+
CATransaction.begin
|
101
|
+
CATransaction.setAnimationDuration 2.0
|
102
|
+
CATransaction.setAnimationTimingFunction CAMediaTimingFunction.functionWithName(KCAMediaTimingFunctionEaseIn)
|
103
|
+
|
104
|
+
radial_animation = CABasicAnimation.animationWithKeyPath("pieRadius")
|
105
|
+
|
106
|
+
radial_animation.fromValue = 0.0
|
107
|
+
radial_animation.toValue = pie_radius
|
108
|
+
radial_animation.duration = 1.0
|
109
|
+
radial_animation.removedOnCompletion = false
|
110
|
+
radial_animation.fillMode = KCAFillModeForwards
|
111
|
+
|
112
|
+
plot.addAnimation(radial_animation, forKey:"pieRadius")
|
113
|
+
|
114
|
+
|
115
|
+
angle_animation = CABasicAnimation.animationWithKeyPath 'angle'
|
116
|
+
angle_animation.fromValue = 0.0
|
117
|
+
angle_animation.toValue = Math::PI/4
|
118
|
+
angle_animation.duration = 1.0
|
119
|
+
angle_animation.removedOnCompletion = false
|
120
|
+
angle_animation.fillMode = KCAFillModeForwards
|
121
|
+
|
122
|
+
plot.addAnimation(angle_animation, forKey:"angle")
|
123
|
+
|
124
|
+
CATransaction.commit
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module MotionPlot
|
2
|
+
class Series
|
3
|
+
|
4
|
+
COLORS = ['4572A7', 'AA4643', '89A54E', '80699B', '3D96AE', 'DB843D', '92A8CD', 'A47D7C', 'B5CA92']
|
5
|
+
|
6
|
+
attr_accessor :name, :data, :index, :type, :style
|
7
|
+
|
8
|
+
def initialize(args={})
|
9
|
+
args.each_pair {|key, value|
|
10
|
+
send("#{key}=", value) if(respond_to?("#{key}="))
|
11
|
+
}
|
12
|
+
|
13
|
+
style_attr = args[:defaults].merge!(color: COLORS[args[:index]])
|
14
|
+
merge_plot_options(style_attr, args[:plot_options])
|
15
|
+
merge_style(style_attr, args[:style])
|
16
|
+
|
17
|
+
@style = Style.new(style_attr)
|
18
|
+
end
|
19
|
+
|
20
|
+
def color
|
21
|
+
@style.color
|
22
|
+
end
|
23
|
+
|
24
|
+
def width
|
25
|
+
@style.width
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def merge_plot_options(style, plot_options)
|
30
|
+
return if(plot_options.nil?)
|
31
|
+
return if(plot_options.send(type).nil?)
|
32
|
+
return if(plot_options.send(type)[:style].nil?)
|
33
|
+
|
34
|
+
merge_style(style, plot_options.send(type)[:style])
|
35
|
+
end
|
36
|
+
|
37
|
+
def merge_style(old_style, new_style)
|
38
|
+
old_style.merge!(new_style) {|key, old_val, new_val|
|
39
|
+
new_val.nil? ? old_val : new_val
|
40
|
+
} if(new_style)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module MotionPlot
|
2
|
+
class Theme
|
3
|
+
DEFAULTS = {
|
4
|
+
:dark_gradient => KCPTDarkGradientTheme,
|
5
|
+
:plain_black => KCPTPlainBlackTheme,
|
6
|
+
:plain_white => KCPTPlainWhiteTheme,
|
7
|
+
:slate => KCPTSlateTheme,
|
8
|
+
:stocks => KCPTStocksTheme
|
9
|
+
}
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def method_missing(m, *args, &block)
|
13
|
+
method_name = m == :default ? :plain_white : m
|
14
|
+
|
15
|
+
raise unless(DEFAULTS.keys.include?(method_name))
|
16
|
+
|
17
|
+
CPTTheme.themeNamed(DEFAULTS[method_name])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module MotionPlot
|
2
|
+
class AnchorPosition
|
3
|
+
DEFAULTS = {
|
4
|
+
:top_right => CPTRectAnchorTopRight,
|
5
|
+
:bottom_left => CPTRectAnchorBottomLeft,
|
6
|
+
:bottom => CPTRectAnchorBottom,
|
7
|
+
:bottom_right => CPTRectAnchorBottomRight,
|
8
|
+
:left => CPTRectAnchorLeft,
|
9
|
+
:right => CPTRectAnchorRight,
|
10
|
+
:top_left => CPTRectAnchorTopLeft,
|
11
|
+
:top => CPTRectAnchorTop,
|
12
|
+
:center => CPTRectAnchorCenter
|
13
|
+
}
|
14
|
+
|
15
|
+
class << self
|
16
|
+
|
17
|
+
def method_missing(m, *args, &block)
|
18
|
+
method_name = m == :default ? :top : m
|
19
|
+
|
20
|
+
raise unless(DEFAULTS.keys.include?(method_name))
|
21
|
+
|
22
|
+
DEFAULTS[method_name]
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module MotionPlot
|
2
|
+
class AreaGradient
|
3
|
+
|
4
|
+
attr_accessor :angle
|
5
|
+
|
6
|
+
def initialize(orientation)
|
7
|
+
@angle = (orientation == "vertical") ? 0.0 : -90.0
|
8
|
+
end
|
9
|
+
|
10
|
+
def fill_with(color)
|
11
|
+
gradient = CPTGradient.gradientWithBeginningColor(color, endingColor:CPTColor.clearColor)
|
12
|
+
gradient.angle = @angle
|
13
|
+
|
14
|
+
CPTFill.fillWithGradient(gradient)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module MotionPlot
|
2
|
+
class Axis
|
3
|
+
attr_accessor :title, :enabled, :type, :labels, :style
|
4
|
+
|
5
|
+
def initialize(args)
|
6
|
+
args.each_pair {|key, value|
|
7
|
+
send("#{key}=", value) if(respond_to?("#{key}="))
|
8
|
+
}
|
9
|
+
|
10
|
+
if(args[:title])
|
11
|
+
@title = Title.new(args[:title])
|
12
|
+
end
|
13
|
+
|
14
|
+
if(args[:style])
|
15
|
+
@style = Style.new(args[:style])
|
16
|
+
else
|
17
|
+
@style = Style.new
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def text_style
|
22
|
+
TextStyle.cpt_text_style(@style)
|
23
|
+
end
|
24
|
+
|
25
|
+
def is_x?
|
26
|
+
type == "xaxis"
|
27
|
+
end
|
28
|
+
|
29
|
+
def is_y?
|
30
|
+
type == "yaxis"
|
31
|
+
end
|
32
|
+
|
33
|
+
def enabled?
|
34
|
+
enabled
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|