File Coverage

blib/lib/Chart/Plotly.pm
Criterion Covered Total %
statement 70 103 67.9
branch 7 24 29.1
condition 3 14 21.4
subroutine 21 25 84.0
pod 6 6 100.0
total 107 172 62.2


line stmt bran cond sub pod time code
1             package Chart::Plotly;
2              
3 6     6   104759 use strict;
  6         13  
  6         170  
4 6     6   28 use warnings;
  6         11  
  6         142  
5 6     6   29 use utf8;
  6         11  
  6         28  
6              
7 6     6   153 use Exporter 'import';
  6         9  
  6         272  
8 6     6   36 use vars qw(@EXPORT_OK);
  6         11  
  6         348  
9             @EXPORT_OK = qw(show_plot);
10              
11 6     6   1354 use JSON;
  6         20734  
  6         48  
12 6     6   4368 use Params::Validate qw(:all);
  6         28991  
  6         1007  
13 6     6   4355 use Text::Template;
  6         21431  
  6         273  
14 6     6   3012 use Module::Load;
  6         6578  
  6         39  
15 6     6   3017 use Ref::Util;
  6         9052  
  6         261  
16 6     6   2605 use HTML::Show;
  6         67667  
  6         195  
17 6     6   8303 use UUID::Tiny ':std';
  6         29512  
  6         1114  
18 6     6   2490 use File::ShareDir;
  6         96421  
  6         265  
19 6     6   42 use Path::Tiny;
  6         13  
  6         5369  
20              
21             our $VERSION = '0.041'; # VERSION
22              
23             # ABSTRACT: Generate html/javascript charts from perl data using javascript library plotly.js
24              
25             sub render_full_html {
26             ## no critic
27 0     0 1 0 my %params = validate( @_, { data => { type => ARRAYREF | OBJECT }, } );
28             ## use critic
29              
30 0         0 my $data = $params{'data'};
31 0         0 my $chart_id = create_uuid_as_string(UUID_TIME);
32 0         0 my $html;
33 0 0 0     0 if ( Ref::Util::is_blessed_ref($data) && $data->isa('Chart::Plotly::Plot') ) {
34 0         0 $html = _render_html_wrap( $data->html( div_id => $chart_id ) );
35             } else {
36 0         0 $html = _render_html_wrap( _render_cell( _process_data($data), $chart_id ) );
37             }
38 0         0 return $html;
39             }
40              
41             sub _render_html_wrap {
42 0     0   0 my $body = shift;
43 0         0 my $html_begin = <<'HTML_BEGIN';
44             <!DOCTYPE html>
45             <head>
46             <meta charset="utf-8" />
47             </head>
48             <body>
49             HTML_BEGIN
50 0         0 my $html_end = <<'HTML_END';
51             </body>
52             </html>
53             HTML_END
54 0         0 return $html_begin . $body . $html_end;
55             }
56              
57             sub _render_cell {
58 1     1   3 my $data_string = shift();
59 1   33     5 my $chart_id = shift() // create_uuid_as_string(UUID_TIME);
60 1         3 my $layout = shift();
61 1         1 my $config = shift();
62 1   50     4 my $extra = shift() // { load_plotly_using_script_tag => 1 };
63 1 50       3 if ( defined $layout ) {
64 1         4 $layout = "," . $layout;
65             }
66 1 50       4 if ( defined $config ) {
67 1         3 $config = "," . $config;
68             }
69 1         3 my $load_plotly = _load_plotly( ${$extra}{'load_plotly_using_script_tag'} );
  1         4  
70 1         2 my $template = <<'TEMPLATE';
71             <div id="{$chart_id}"></div>
72             {$load_plotly}
73             <script>
74             Plotly.{$plotlyjs_plot_function}(document.getElementById('{$chart_id}'),{$data} {$layout} {$config});
75             </script>
76             TEMPLATE
77              
78 1 50       4 my $template_variables = { data => $data_string,
    50          
79             chart_id => $chart_id,
80             load_plotly => $load_plotly,
81             plotlyjs_plot_function => plotlyjs_plot_function(),
82             defined $layout ? ( layout => $layout ) : (),
83             defined $config ? ( config => $config ) : (),
84             };
85 1         4 return Text::Template::fill_in_string( $template, HASH => $template_variables );
86             }
87              
88             sub _process_data {
89 6     6   10 my $data = shift;
90 6         50 my $json_formatter = JSON->new->allow_blessed(1)->convert_blessed(1);
91 6     2   30 local *PDL::TO_JSON = sub { $_[0]->unpdl };
  2         69  
92 6 50       18 if ( Ref::Util::is_blessed_ref($data) ) {
93 0         0 my $adapter_name = 'Chart::Plotly::Adapter::' . ref $data;
94 0         0 eval {
95 0         0 load $adapter_name;
96 0         0 my $adapter = $adapter_name->new( data => $data );
97 0         0 $data = $adapter->traces();
98             };
99 0 0       0 if ($@) {
100 0         0 warn 'Cannot load adapter: ' . $adapter_name . '. ' . $@;
101             }
102             }
103 6         43 my $data_string = $json_formatter->encode($data);
104 6         74 return $data_string;
105             }
106              
107             sub _load_plotly {
108 1     1   2 my $how_to_load = shift;
109 1 50       5 if ($how_to_load) {
110 1 50 33     6 if ( $how_to_load eq "1" || $how_to_load eq 'cdn' ) {
    0          
    0          
111 1         3 return '<script src="https://cdn.plot.ly/plotly-' . plotlyjs_version() . '.min.js"></script>';
112             } elsif ( $how_to_load eq 'embed' ) {
113 0         0 my $minified_plotly = File::ShareDir::dist_file( 'Chart-Plotly', 'plotly.js/plotly.min.js' );
114 0         0 return '<script>' . Path::Tiny::path($minified_plotly)->slurp . '</script>';
115             } elsif ( $how_to_load eq 'module_dist' ) {
116 0         0 my $minified_plotly = File::ShareDir::dist_file( 'Chart-Plotly', 'plotly.js/plotly.min.js' );
117 0         0 return '<script src="file://' . $minified_plotly . '"></script>';
118             }
119             } else {
120 0         0 return '';
121             }
122             }
123              
124             sub html_plot {
125 0     0 1 0 my @data_to_plot = @_;
126              
127 0         0 my $rendered_cells = "";
128 0         0 for my $data (@data_to_plot) {
129 0         0 my $id = create_uuid_as_string(UUID_TIME);
130 0 0 0     0 if ( Ref::Util::is_blessed_ref($data) && $data->isa('Chart::Plotly::Plot') ) {
131 0         0 $rendered_cells .= $data->html( div_id => $id );
132             } else {
133 0         0 $rendered_cells .= _render_cell( _process_data($data), $id );
134             }
135             }
136 0         0 return _render_html_wrap($rendered_cells);
137             }
138              
139             sub show_plot {
140 0     0 1 0 HTML::Show::show( html_plot(@_) );
141             }
142              
143             sub plotlyjs_version {
144 1     1 1 6 return '1.52.2'; # plotlyjs_version_tag
145             }
146              
147             sub plotlyjs_plot_function {
148 3     3 1 19 return 'react';
149             }
150              
151             sub plotlyjs_plot_function_parameters {
152 1     1 1 7 return qw(div data layout config);
153             }
154              
155             1;
156              
157             __END__
158              
159             =pod
160              
161             =encoding utf-8
162              
163             =head1 NAME
164              
165             Chart::Plotly - Generate html/javascript charts from perl data using javascript library plotly.js
166              
167             =head1 VERSION
168              
169             version 0.041
170              
171             =head1 SYNOPSIS
172              
173             use Chart::Plotly 'show_plot';
174            
175             my $data = { x => [ 1 .. 10 ],
176             mode => 'markers',
177             type => 'scatter'
178             };
179             $data->{'y'} = [ map { rand 10 } @{ $data->{'x'} } ];
180            
181             show_plot([$data]);
182            
183             use aliased 'Chart::Plotly::Trace::Scattergl';
184            
185             my $big_array = [ 1 .. 10000 ];
186             my $scattergl = Scattergl->new( x => $big_array, y => [ map { rand 100 } @$big_array ] );
187            
188             show_plot([$scattergl]);
189              
190             use Chart::Plotly qw(show_plot);
191             use PDL;
192            
193             use aliased 'Chart::Plotly::Trace::Surface';
194            
195             my $size = 25;
196             my $x = ( xvals zeroes $size+ 1, $size + 1 ) / $size;
197             my $y = ( yvals zeroes $size+ 1, $size + 1 ) / $size;
198             my $z = 0.5 + 0.5 * ( sin( $x * 6.3 ) * sin( $y * 6.3 ) )**3; # Bumps
199            
200             my $surface = Surface->new( x => $x, y => $y, z => $z );
201            
202             show_plot([$surface]);
203            
204             use PDL::Math;
205            
206             my $bessel_size = 50;
207             my $bessel = Surface->new(
208             x => xvals($bessel_size),
209             y => xvals($bessel_size),
210             z => bessj0( rvals( zeroes( $bessel_size, $bessel_size ) ) / 2 )
211             );
212            
213             show_plot([$bessel]);
214              
215             =head1 DESCRIPTION
216              
217             Generate html/javascript charts from perl data using javascript library plotly.js. The result
218             is a file that you could see in your favourite browser.
219              
220             =for markdown [![Build Status](https://travis-ci.org/pablrod/p5-Chart-Plotly.png?branch=master)](https://travis-ci.org/pablrod/p5-Chart-Plotly)
221             [![Build status](https://ci.appveyor.com/api/projects/status/wbur95v3sjk4mv6d/branch/master?svg=true)](https://ci.appveyor.com/project/pablrod/p5-chart-plotly/branch/master)
222              
223             Example screenshot of plot generated with examples/anscombe.pl:
224              
225             =for HTML <p>
226             <img src="https://raw.githubusercontent.com/pablrod/p5-Chart-Plotly/master/examples/anscombe.png" alt="Anscombe's quartet plotted with plotly">
227             </p>
228              
229             =for markdown ![Anscombe's quartet plotted with plotly](https://raw.githubusercontent.com/pablrod/p5-Chart-Plotly/master/examples/anscombe.png)
230              
231             Example screenshot of plots generated with examples/traces/*.pl:
232              
233             =for HTML <p>
234             <img src="https://raw.githubusercontent.com/pablrod/p5-Chart-Plotly/master/examples/montage_all_traces.png" alt="Montage of all examples">
235             </p>
236              
237             =for markdown ![Montage of all examples](https://raw.githubusercontent.com/pablrod/p5-Chart-Plotly/master/examples/montage_all_traces.png)
238              
239             The API is subject to changes.
240              
241             =head1 FUNCTIONS
242              
243             =head2 render_full_html
244              
245             =head3 Parameters
246              
247             =over
248              
249             =item * data:
250              
251             Data to be represented. It could be:
252              
253             =over
254              
255             =item Perl data structure of the json expected by plotly.js: L<http://plot.ly/javascript/reference/> (this data would be serialized to JSON)
256              
257             =item Array ref of objects of type Chart::Plotly::Trace::*
258              
259             =item Anything that could be serialized to JSON with the json expected by plotly.js
260              
261             =item Object that could be adapted using Chart::Plotly::Adapter::*
262              
263             =back
264              
265             =back
266              
267             =head2 html_plot
268              
269             Return the html for the plot or plots
270              
271             =head3 Parameters
272              
273             Data to be represented. The format is the same as the parameter data in render_full_html. Accepts multiple traces/plots/objects.
274              
275             =head2 show_plot
276              
277             Opens the plot or plots in a browser locally
278              
279             =head3 Parameters
280              
281             Data to be represented. The format is the same as the parameter data in render_full_html. Accepts multiple traces/plots/objects.
282              
283             =head2 plotlyjs_version
284              
285             Returns the version of plotly.js using in this version of the perl module as a string
286              
287             =head2 plotlyjs_plot_function
288              
289             Returns the name of function of plotly.js used in this version of the perl module to draw plots
290              
291             =head2 plotlyjs_plot_function_parameters
292              
293             Returns the function parameters of the function of plotly.js used in this version of the perl module to draw plots as a list of strings
294              
295             =head1 BUGS
296              
297             Please report any bugs or feature requests via github: L<https://github.com/pablrod/p5-Chart-Plotly/issues>
298              
299             =head1 DISCLAIMER
300              
301             This is an unofficial Plotly Perl module. Currently I'm not affiliated in any way with Plotly.
302             But I think plotly.js is a great library and I want to use it with perl.
303              
304             If you like plotly.js please consider supporting them purchasing a pro subscription: L<https://plot.ly/products/cloud/>
305              
306             =head1 AUTHOR
307              
308             Pablo Rodríguez González <pablo.rodriguez.gonzalez@gmail.com>
309              
310             =head1 COPYRIGHT AND LICENSE
311              
312             This software is Copyright (c) 2020 by Pablo Rodríguez González.
313              
314             This is free software, licensed under:
315              
316             The MIT (X11) License
317              
318             =head1 CONTRIBUTORS
319              
320             =for stopwords Roy Storey Stephan Loyd weatherwax
321              
322             =over 4
323              
324             =item *
325              
326             Roy Storey <kiwiroy@users.noreply.github.com>
327              
328             =item *
329              
330             Stephan Loyd <stephanloyd9@gmail.com>
331              
332             =item *
333              
334             weatherwax <s.g.lobo@hotmail.com>
335              
336             =back
337              
338             =cut