motion-plot 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|