added simple math and charts modules
This commit is contained in:
97
web/modules/charts/.gitlab-ci.yml
Normal file
97
web/modules/charts/.gitlab-ci.yml
Normal file
@@ -0,0 +1,97 @@
|
||||
################
|
||||
# DrupalCI GitLabCI template
|
||||
#
|
||||
# Gitlab-ci.yml to replicate DrupalCI testing for Contrib
|
||||
#
|
||||
# With thanks to:
|
||||
# * The GitLab Acceleration Initiative participants
|
||||
# * DrupalSpoons
|
||||
################
|
||||
|
||||
################
|
||||
# Guidelines
|
||||
#
|
||||
# This template is designed to give any Contrib maintainer everything they need to test, without requiring modification. It is also designed to keep up to date with Core Development automatically through the use of include files that can be centrally maintained.
|
||||
#
|
||||
# However, you can modify this template if you have additional needs for your project.
|
||||
################
|
||||
|
||||
################
|
||||
# Includes
|
||||
#
|
||||
# Additional configuration can be provided through includes.
|
||||
# One advantage of include files is that if they are updated upstream, the changes affect all pipelines using that include.
|
||||
#
|
||||
# Includes can be overridden by re-declaring anything provided in an include, here in gitlab-ci.yml
|
||||
# https://docs.gitlab.com/ee/ci/yaml/includes.html#override-included-configuration-values
|
||||
################
|
||||
|
||||
include:
|
||||
################
|
||||
# DrupalCI includes:
|
||||
# As long as you include this, any future includes added by the Drupal Association will be accessible to your pipelines automatically.
|
||||
# View these include files at https://git.drupalcode.org/project/gitlab_templates/
|
||||
################
|
||||
- project: $_GITLAB_TEMPLATES_REPO
|
||||
ref: $_GITLAB_TEMPLATES_REF
|
||||
file:
|
||||
- '/includes/include.drupalci.main.yml'
|
||||
- '/includes/include.drupalci.variables.yml'
|
||||
- '/includes/include.drupalci.workflows.yml'
|
||||
|
||||
################
|
||||
# Pipeline configuration variables
|
||||
#
|
||||
# These are the variables provided to the Run Pipeline form that a user may want to override.
|
||||
#
|
||||
# Docs at https://git.drupalcode.org/project/gitlab_templates/-/blob/1.0.x/includes/include.drupalci.variables.yml
|
||||
################
|
||||
# variables:
|
||||
# SKIP_ESLINT: '1'
|
||||
|
||||
|
||||
###################################################################################
|
||||
#
|
||||
# *
|
||||
# /(
|
||||
# ((((,
|
||||
# /(((((((
|
||||
# ((((((((((*
|
||||
# ,(((((((((((((((
|
||||
# ,(((((((((((((((((((
|
||||
# ((((((((((((((((((((((((*
|
||||
# *(((((((((((((((((((((((((((((
|
||||
# ((((((((((((((((((((((((((((((((((*
|
||||
# *(((((((((((((((((( .((((((((((((((((((
|
||||
# ((((((((((((((((((. /(((((((((((((((((*
|
||||
# /((((((((((((((((( .(((((((((((((((((,
|
||||
# ,(((((((((((((((((( ((((((((((((((((((
|
||||
# .(((((((((((((((((((( .(((((((((((((((((
|
||||
# ((((((((((((((((((((((( ((((((((((((((((/
|
||||
# (((((((((((((((((((((((((((/ ,(((((((((((((((*
|
||||
# .((((((((((((((/ /(((((((((((((. ,(((((((((((((((
|
||||
# *(((((((((((((( ,(((((((((((((/ *((((((((((((((.
|
||||
# ((((((((((((((, /(((((((((((((. ((((((((((((((,
|
||||
# (((((((((((((/ ,(((((((((((((* ,(((((((((((((,
|
||||
# *((((((((((((( .((((((((((((((( ,(((((((((((((
|
||||
# ((((((((((((/ /((((((((((((((((((. ,((((((((((((/
|
||||
# ((((((((((((( *(((((((((((((((((((((((* *((((((((((((
|
||||
# ((((((((((((( ,(((((((((((((..((((((((((((( *((((((((((((
|
||||
# ((((((((((((, /((((((((((((* /((((((((((((/ ((((((((((((
|
||||
# ((((((((((((( /((((((((((((/ (((((((((((((* ((((((((((((
|
||||
# (((((((((((((/ /(((((((((((( ,((((((((((((, *((((((((((((
|
||||
# (((((((((((((( *(((((((((((/ *((((((((((((. ((((((((((((/
|
||||
# *((((((((((((((((((((((((((, /(((((((((((((((((((((((((
|
||||
# ((((((((((((((((((((((((( ((((((((((((((((((((((((,
|
||||
# .(((((((((((((((((((((((/ ,(((((((((((((((((((((((
|
||||
# ((((((((((((((((((((((/ ,(((((((((((((((((((((/
|
||||
# *((((((((((((((((((((( (((((((((((((((((((((,
|
||||
# ,(((((((((((((((((((((, ((((((((((((((((((((/
|
||||
# ,(((((((((((((((((((((* /((((((((((((((((((((
|
||||
# ((((((((((((((((((((((, ,/((((((((((((((((((((,
|
||||
# ,(((((((((((((((((((((((((((((((((((((((((((((((((((
|
||||
# .(((((((((((((((((((((((((((((((((((((((((((((
|
||||
# .((((((((((((((((((((((((((((((((((((,.
|
||||
# .,(((((((((((((((((((((((((.
|
||||
#
|
||||
###################################################################################
|
||||
339
web/modules/charts/LICENSE.txt
Normal file
339
web/modules/charts/LICENSE.txt
Normal file
@@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
242
web/modules/charts/README.md
Executable file
242
web/modules/charts/README.md
Executable file
@@ -0,0 +1,242 @@
|
||||
# Charts
|
||||
|
||||
Transform **data** into **information**. The Charts module for Drupal enables
|
||||
you to build dynamic charts without needing to write a line of code. If you are
|
||||
comfortable coding, you can use the Charts API to generate or update charts.
|
||||
Instructions for creating charts are included below.
|
||||
|
||||
There are many charting libraries (also sometimes referred to as providers)
|
||||
that you can find online. Each has their own benefits, drawbacks, and APIs. To
|
||||
use one of them **without** this module, you would need to 1) be familiar with
|
||||
the charting library's API, 2) add its JavaScript file(s) in your page, and 3)
|
||||
write code for every chart you want to include. That's a lot of effort, and it
|
||||
requires knowledge and access that many Drupal site builders will not have.
|
||||
|
||||
Charts is designed so that anyone who wants a chart on their site can have a
|
||||
chart -- and be happy with it.
|
||||
|
||||
## How Does The Charts Module Work (Technically)?
|
||||
|
||||
The Charts module takes data and configuration from your site, and with the help
|
||||
of a submodule, organizes it into a JSON object that is saved as an attribute
|
||||
on an HTML element on your page; the JSON objected is handed to your selected
|
||||
charting library, which renders it into a chart.
|
||||
|
||||
## Charting Providers / Libraries
|
||||
|
||||
Out of the box, you will be able to use 5 charting solutions (referred to as
|
||||
"providers" or "libraries". Each of them has particular advantages and
|
||||
disadvantages.
|
||||
|
||||
* Billboard.js: This library is a fork of the C3 charting library. It has
|
||||
additional features, such as radar charts.
|
||||
* C3.js: This library is a D3-based reusable chart library that makes it easy
|
||||
to generate D3-based charts.
|
||||
* Chart.js: This is a simple yet flexible JavaScript charting for designers &
|
||||
developers.
|
||||
* Google Charts: This library generates interactive charts using SVG and VML.
|
||||
* Highcharts: This library is one of the premier solutions for generating
|
||||
charts. Although it is very powerful and aesthetically pleasing with smooth
|
||||
animations, it requires a commercial license. It's free for non-commercial
|
||||
use. See http://www.highcharts.com
|
||||
|
||||
## Chart Types
|
||||
|
||||
Each charting library has its own set of chart types. The Charts module
|
||||
focuses on chart types that are available in *most* of the libraries.
|
||||
To avoid a confusing user interface, the Charts module no longer includes
|
||||
chart types that are not supported by the selected library in the Chart
|
||||
Type selection. For example, if you select the Chart.js library, you will
|
||||
not see the "Gauge" chart type.
|
||||
|
||||
## Installing Libraries
|
||||
|
||||
All the Charts submodules default to using a content delivery network (CDN) to
|
||||
pull in the necessary JavaScript files unless a local copy is present, or you
|
||||
have disabled the CDN option on the "Charts configuration" page
|
||||
(`/admin/config/content/charts`).
|
||||
|
||||
Below are a couple outlines for adding the libraries locally.
|
||||
|
||||
### Using Composer
|
||||
|
||||
1. Ensure that you have the `composer/installers` package installed.
|
||||
2. Ensure you have an installer-paths for the drupal-library type. Such as in
|
||||
the composer.json of
|
||||
https://github.com/drupal-composer/drupal-project/blob/9.x/composer.json
|
||||
3. In each submodule, there is a README.md file that has code to add to your
|
||||
site's composer.json file.
|
||||
4. After updating your project's composer.json file, run the 'composer require'
|
||||
command specified in each submodule's README.md file. For example, if you are
|
||||
using Google Charts, step three would mean adding:
|
||||
```json
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "google/charts",
|
||||
"version": "45",
|
||||
"type": "drupal-library",
|
||||
"extra": {
|
||||
"installer-name": "google_charts"
|
||||
},
|
||||
"dist": {
|
||||
"url": "https://www.gstatic.com/charts/loader.js",
|
||||
"type": "file"
|
||||
},
|
||||
"require": {
|
||||
"composer/installers": "^1.0 || ^2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
to your project's composer.json, and then running:
|
||||
`composer require --prefer-dist google/charts:45`
|
||||
|
||||
### Using Composer and wikimedia/composer-merge-plugin:
|
||||
|
||||
1. Ensure that you have the `wikimedia/composer-merge-plugin` package
|
||||
installed.
|
||||
2. Ensure that you have the `oomphinc/composer-installers-extender` package
|
||||
installed.
|
||||
3. Add an "installer-types" section in the "extra" of your project
|
||||
composer.json file, make sure you have "npm-asset" listed. For example:
|
||||
```json
|
||||
"installer-types": [
|
||||
"npm-asset"
|
||||
],
|
||||
```
|
||||
5. In the "installer-paths" section in the "extra" of your project
|
||||
composer.json file, ensure you have the types drupal-library and npm-asset.
|
||||
For example:
|
||||
```json
|
||||
"web/libraries/{$name}": [
|
||||
"type:drupal-library",
|
||||
"type:npm-asset"
|
||||
],
|
||||
```
|
||||
6. Add a "merge-plugin" section in the "extra" of your project composer.json
|
||||
file, so that the composer.json file of the submodules you want is included.
|
||||
For example:
|
||||
```json
|
||||
"merge-plugin": {
|
||||
"include": [
|
||||
"web/modules/contrib/charts/modules/charts_highcharts/composer.json"
|
||||
]
|
||||
},
|
||||
```
|
||||
7. Run the `composer require` specified in the submodule's README.md file
|
||||
|
||||
## Creating Charts in the UI
|
||||
|
||||
This module provides a configuration page at /admin/config/content/charts.
|
||||
You may set site-wide defaults on this page (for example: set the default color
|
||||
scheme). It also has library-specific configuration available after saving a
|
||||
default library.
|
||||
|
||||
There are three options for creating a chart within the UI:
|
||||
1. Using Views
|
||||
2. Using a Chart Field
|
||||
3. Using a Chart Block
|
||||
|
||||
### Creating Charts with Views
|
||||
|
||||
1. Create a new view:
|
||||
Visit admin/structure/views/add and select the display format of "Chart" for
|
||||
your new page or block.
|
||||
|
||||
2. Add a label field:
|
||||
Under the "Fields" section, add a field you would like to be used as labels
|
||||
along one axis of the chart (or slices of the pie). If you are visualizing
|
||||
an Event content type, you might select `title`.
|
||||
|
||||
3. Add a data field or fields:
|
||||
Now add a second field that will be used to determine the data values. If you
|
||||
are visualizing an Event content type, this field might be
|
||||
`field_number_attendees`. The label you give this field will be used in the
|
||||
chart's legend to represent this series. Do this again for each different
|
||||
quantity you would like to chart. Note that some chart types (e.g. "pie")
|
||||
only support a single data column.
|
||||
|
||||
4. Configure the chart display:
|
||||
Click on the "Settings" link in the "Format" section to configure the chart.
|
||||
Select your chart library, type, the label field, and the data provider.
|
||||
There are many other options to customize your chart. Some options may not
|
||||
be available to all chart types and will be adjusted based on the type
|
||||
selected.
|
||||
|
||||
5. Save your view.
|
||||
|
||||
As described in the steps above - if you have a field representing a number, you
|
||||
do not necessarily need to aggregate your view. If the content you want to
|
||||
visualize does not include a numeric field, you may need to aggregate your view
|
||||
and use the `count` function to generate a number for your data field.
|
||||
|
||||
**Tip:** You may find it easier to start with a "Table" display and convert it
|
||||
to a chart display after setting up the data. It can be easier to visualize
|
||||
what the result of the chart will be if it's been laid out in a table first.
|
||||
|
||||
### Creating Combo Charts
|
||||
|
||||
If you are creating your chart in Views and want to a chart that includes
|
||||
multiple chart types (such as columns and a line - aka a "combo" chart), add a
|
||||
new display of type "Chart attachment". Use the middle column of this display
|
||||
to attach the "Chart attachment" to a parent display (this is required). In
|
||||
this section, you can also instruct the "Chart attachment" to inherit exposed
|
||||
or contextual filters and if it should use the primary y-axis or a secondary
|
||||
y-axis. You still need to configure the "Settings" in the "Format" section.
|
||||
|
||||
## Creating Charts in the UI with a Chart Field
|
||||
|
||||
If you want a chart in your entity (e.g. "node"), but don't need its data to be
|
||||
dynamic (like Views could generate), you can use a Chart field. Add this field
|
||||
to your bundle (e.g. "content type") via the "Manage Fields" tab - like you
|
||||
would any other field. When you add a new entity, you can select the charting
|
||||
library, chart type, and other configurations. You can add data using a CSV or
|
||||
by manually adding it into the table input. The chart will show on your entity
|
||||
after saving.
|
||||
|
||||
## Creating Charts in the UI with a Chart Block
|
||||
|
||||
If you are using Layout Builder or want to place a chart on several pages, and
|
||||
the chart does not need dynamic data (like Views could generate), you can use a
|
||||
Chart Block. When you add a Chart Block, you can select the charting library,
|
||||
chart type, and other configurations. You can add data using a CSV or by
|
||||
manually adding it into the table input.
|
||||
|
||||
## Creating Charts Using the API
|
||||
|
||||
Charts are elements that can be rendered using a Drupal render array. Please
|
||||
refer to the `charts.api.php` file and also look at the included submodule,
|
||||
`charts_api_examples` (specifically the controller, which has many examples).
|
||||
|
||||
## Debugging Your Charts
|
||||
|
||||
There is a checkbox in the "Advanced" tab of the config form
|
||||
(/admin/config/content/charts/advanced) that enabled debugging. Checking this
|
||||
box will enable a collapsible "details" element that includes the JSON
|
||||
object generated by the Charts module - this can often be added directly
|
||||
into a code playground provided by the charting library. If you are
|
||||
implementing your own Charts submodule, you can add a line like this into
|
||||
your charts_MYMODULE.js file (it may need modification):
|
||||
```
|
||||
if (element.nextElementSibling &&
|
||||
element.nextElementSibling.hasAttribute('data-charts-debug-container')) {
|
||||
element.nextElementSibling.querySelector('code').innerText = JSON.stringify(
|
||||
config,
|
||||
null,
|
||||
' '
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
For bug reports and feature requests please use the Drupal.org issue tracker:
|
||||
https://www.drupal.org/project/issues/charts.
|
||||
|
||||
We welcome your support in improving code documentation, tests, and providing
|
||||
example use-cases not addressed by the existing module.
|
||||
|
||||
If you are interested in creating your own submodule for a library not
|
||||
currently supported, please contact
|
||||
[@andileco](https://www.drupal.org/u/andileco).
|
||||
174
web/modules/charts/charts.api.php
Normal file
174
web/modules/charts/charts.api.php
Normal file
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Documentation on hooks provided by the Charts module.
|
||||
*
|
||||
* Charts module provides 4 element types that can be used to construct a chart.
|
||||
* In its most basic form, a chart may be created by specifying an element
|
||||
* with the #type property "chart". However, for enough information to
|
||||
* accurately data to an audience, you need a little more. Below is a very
|
||||
* basic example of a column chart.
|
||||
*
|
||||
* @code
|
||||
* $chart = [
|
||||
* '#type' => 'chart',
|
||||
* '#chart_type' => 'column',
|
||||
* 'series' => [
|
||||
* '#type' => 'chart_data',
|
||||
* '#title' => t('Responses'),
|
||||
* '#data' => [60, 40],
|
||||
* ],
|
||||
* 'xaxis' => [
|
||||
* '#type' => 'chart_xaxis',
|
||||
* '#labels' => [t('Yes'), t('No')],
|
||||
* ]
|
||||
* ];
|
||||
* @endcode
|
||||
*
|
||||
* On charts that have multiple axes, you'll need to add individual sub-elements
|
||||
* for each series of data. If you desire, you may also customize the axes by
|
||||
* providing an axis element too.
|
||||
*
|
||||
* @code
|
||||
* $chart = [
|
||||
* '#type' => 'chart',
|
||||
* '#chart_type' => 'column',
|
||||
* ];
|
||||
* $chart['series'] = [
|
||||
* '#type' => 'chart_data',
|
||||
* '#title' => t('Responses'),
|
||||
* '#data' => [10, 20, 30],
|
||||
* ];
|
||||
* $chart['xaxis'] = [
|
||||
* '#type' => 'chart_xaxis',
|
||||
* '#title' => t('Month'),
|
||||
* '#labels' => [t('Jan'), t('Feb'), t('Mar')],
|
||||
* ];
|
||||
* @endcode
|
||||
*
|
||||
* Once you have generated a chart object, you can run
|
||||
* \Drupal::service('renderer')->render() on it to turn it into HTML:
|
||||
*
|
||||
* @code
|
||||
* $output = \Drupal::service('renderer')->render($chart);
|
||||
* @endcode
|
||||
*
|
||||
* There are many, many properties available for the four chart types (chart,
|
||||
* chart_data, chart_xaxis, and chart_yaxis). For a full list, see the
|
||||
* charts_element_info() function.
|
||||
*
|
||||
* This module also includes a number of examples for reference in the
|
||||
* charts_api_example module, and displayed at /charts/example/display.
|
||||
*
|
||||
* @see Drupal\charts\Element folder
|
||||
*/
|
||||
|
||||
/**
|
||||
* Alter an individual chart before it is printed.
|
||||
*
|
||||
* @param array $element
|
||||
* The chart renderable. Passed in by reference.
|
||||
* @param string $chart_id
|
||||
* The chart identifier, pulled from the $chart['#chart_id'] property
|
||||
* (if any). Not all charts have a chart identifier.
|
||||
*/
|
||||
function hook_chart_alter(array &$element, $chart_id) {
|
||||
if ($chart_id === 'view_name__display_name') {
|
||||
// Individual properties may be modified.
|
||||
$element['#title_font_size'] = 20;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter an individual chart before it's rendered.
|
||||
*
|
||||
* Same as hook_chart_alter(), only including the $chart_id in the function
|
||||
* name instead of being passed in as an argument.
|
||||
*
|
||||
* @see hook_chart_alter()
|
||||
*/
|
||||
function hook_chart_CHART_ID_alter(array &$element) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter an individual chart's raw library representation.
|
||||
*
|
||||
* This hook is called AFTER hook_chart_alter(), after Charts module has
|
||||
* converted the renderable into the chart definition that will be used by the
|
||||
* library. Note that the structure of $definition will differ based on the
|
||||
* charting library used. Switching charting libraries may cause your code
|
||||
* to break when using this hook.
|
||||
*
|
||||
* Even though this hook may be fragile, it may provide developers with access
|
||||
* to library-specific functionality.
|
||||
*
|
||||
* @param array $definition
|
||||
* The chart definition to be modified. The raw values are passed directly to
|
||||
* the charting library.
|
||||
* @param array $element
|
||||
* The chart renderable. This may be used for reference (or read to add
|
||||
* support for new properties), but any changes to this variable will not
|
||||
* have an effect on output.
|
||||
* @param string $chart_id
|
||||
* The chart ID, derived from the $chart['#chart_id'] property. Note that not
|
||||
* all charts may have a $chart_id.
|
||||
*/
|
||||
function hook_chart_definition_alter(array &$definition, array $element, $chart_id) {
|
||||
if ($element['#chart_library'] === 'google') {
|
||||
$definition['options']['titleTextStyle']['fontSize'] = 20;
|
||||
}
|
||||
elseif ($element['#chart_library'] === 'highcharts') {
|
||||
$definition['title']['style']['fontSize'] = 20;
|
||||
// Add a caption based on a field on your node containing a Chart field.
|
||||
$node = $element['#entity'];
|
||||
if ($node instanceof \Drupal\node\NodeInterface && $node->hasField('field_caption')) {
|
||||
$definition['caption']['text'] = $node->field_caption->value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter an individual chart before it's rendered.
|
||||
*
|
||||
* Same as hook_chart_definition_alter(), only including the $chart_id in the
|
||||
* function name instead of being passed in as an argument.
|
||||
*
|
||||
* @see hook_chart_definition_alter()
|
||||
*/
|
||||
function hook_chart_definition_CHART_ID_alter(array &$definition, array $element, $chart_id) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the list of supported chart types for a chart plugin.
|
||||
*
|
||||
* To use this hook, first define the chart types you want to add in a file
|
||||
* named <module>.charts_types.yml to your module's root directory. For example,
|
||||
* if your module is named "charts_foo", the file should be named
|
||||
* "charts_foo.charts_types.yml".
|
||||
*
|
||||
* The contents of this file will look something like:
|
||||
*
|
||||
* @code
|
||||
* candlestick:
|
||||
* label: 'Candlestick'
|
||||
* axis: 'xy'
|
||||
* axis_inverted: false
|
||||
* stacking: false
|
||||
* @endcode
|
||||
*
|
||||
* @see charts.charts_types.yml
|
||||
*
|
||||
* With that file in place, you can now implement this hook. See below for an
|
||||
* example adding the 'candlestick' chart type to the 'highcharts' chart plugin.
|
||||
*
|
||||
* @param array $types
|
||||
* An array of supported chart types.
|
||||
* @param string $chart_plugin_id
|
||||
* The chart plugin ID.
|
||||
*/
|
||||
function hook_charts_plugin_supported_chart_types_alter(array &$types, string $chart_plugin_id) {
|
||||
if ($chart_plugin_id === 'highcharts') {
|
||||
$types[] = 'candlestick';
|
||||
}
|
||||
}
|
||||
50
web/modules/charts/charts.charts_types.yml
Executable file
50
web/modules/charts/charts.charts_types.yml
Executable file
@@ -0,0 +1,50 @@
|
||||
area:
|
||||
label: 'Area'
|
||||
axis: 'xy'
|
||||
axis_inverted: false
|
||||
stacking: true
|
||||
bar:
|
||||
label: 'Bar'
|
||||
axis: 'xy'
|
||||
axis_inverted: true
|
||||
stacking: true
|
||||
bubble:
|
||||
label: 'Bubble'
|
||||
axis: 'xy'
|
||||
axis_inverted: false
|
||||
stacking: false
|
||||
column:
|
||||
label: 'Column'
|
||||
axis: 'xy'
|
||||
axis_inverted: false
|
||||
stacking: true
|
||||
donut:
|
||||
label: 'Donut'
|
||||
axis: 'y_only'
|
||||
axis_inverted: false
|
||||
stacking: false
|
||||
gauge:
|
||||
label: 'Gauge'
|
||||
axis: 'y_only'
|
||||
axis_inverted: false
|
||||
stacking: false
|
||||
line:
|
||||
label: 'Line'
|
||||
axis: 'xy'
|
||||
axis_inverted: false
|
||||
stacking: false
|
||||
pie:
|
||||
label: 'Pie'
|
||||
axis: 'y_only'
|
||||
axis_inverted: false
|
||||
stacking: false
|
||||
scatter:
|
||||
label: 'Scatter'
|
||||
axis: 'xy'
|
||||
axis_inverted: false
|
||||
stacking: false
|
||||
spline:
|
||||
label: 'Spline'
|
||||
axis: 'xy'
|
||||
axis_inverted: false
|
||||
stacking: false
|
||||
5
web/modules/charts/charts.config_translation.yml
Normal file
5
web/modules/charts/charts.config_translation.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
charts.settings:
|
||||
title: 'Charts settings'
|
||||
base_route_name: charts.settings
|
||||
names:
|
||||
- charts.settings
|
||||
11
web/modules/charts/charts.info.yml
Normal file
11
web/modules/charts/charts.info.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
name: Charts
|
||||
type: module
|
||||
description: 'A charting API for Drupal that provides chart elements and integration with Views.'
|
||||
package: Charts
|
||||
core_version_requirement: ^10.3 || ^11
|
||||
configure: charts.settings
|
||||
|
||||
# Information added by Drupal.org packaging script on 2024-12-02
|
||||
version: '5.1.1'
|
||||
project: 'charts'
|
||||
datestamp: 1733170633
|
||||
15
web/modules/charts/charts.install
Executable file
15
web/modules/charts/charts.install
Executable file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install and update functions for the Charts module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_requirements().
|
||||
*/
|
||||
function charts_requirements($phase) {
|
||||
$requirements = [];
|
||||
|
||||
return $requirements;
|
||||
}
|
||||
7
web/modules/charts/charts.libraries.yml
Normal file
7
web/modules/charts/charts.libraries.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
global:
|
||||
version: VERSION
|
||||
js:
|
||||
js/charts.js: {}
|
||||
dependencies:
|
||||
- core/drupal
|
||||
5
web/modules/charts/charts.links.menu.yml
Executable file
5
web/modules/charts/charts.links.menu.yml
Executable file
@@ -0,0 +1,5 @@
|
||||
charts.settings:
|
||||
title: 'Charts configuration'
|
||||
description: 'Set the default library, behavior, and style of charts.'
|
||||
parent: system.admin_config_content
|
||||
route_name: charts.settings
|
||||
16
web/modules/charts/charts.links.task.yml
Executable file
16
web/modules/charts/charts.links.task.yml
Executable file
@@ -0,0 +1,16 @@
|
||||
charts.settings:
|
||||
title: 'Charts Settings'
|
||||
route_name: charts.settings
|
||||
base_route: charts.settings
|
||||
|
||||
charts.settings.default:
|
||||
title: 'Default'
|
||||
route_name: charts.settings
|
||||
parent_id: charts.settings
|
||||
weight: 10
|
||||
|
||||
charts.settings.advanced:
|
||||
title: 'Advanced'
|
||||
route_name: charts.settings.advanced
|
||||
parent_id: charts.settings
|
||||
weight: 20
|
||||
230
web/modules/charts/charts.module
Executable file
230
web/modules/charts/charts.module
Executable file
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Charts - Module.
|
||||
*/
|
||||
|
||||
use Drupal\views\ViewExecutable;
|
||||
|
||||
/**
|
||||
* Implements hook_views_data().
|
||||
*/
|
||||
function charts_views_data() {
|
||||
$data['charts_fields']['table']['group'] = t('Charts');
|
||||
$data['charts_fields']['table']['join'] = [
|
||||
// Exist in all views.
|
||||
'#global' => [],
|
||||
];
|
||||
$data['charts_fields']['field_charts_fields_scatter'] = [
|
||||
'title' => t('Scatter Field'),
|
||||
'help' => t('Use this field for your data field in a scatter plot.'),
|
||||
'field' => [
|
||||
'id' => 'field_charts_fields_scatter',
|
||||
],
|
||||
];
|
||||
$data['charts_fields']['field_charts_fields_bubble'] = [
|
||||
'title' => t('Bubble Field'),
|
||||
'help' => t('Use this field for your data field in a bubble chart.'),
|
||||
'field' => [
|
||||
'id' => 'field_charts_fields_bubble',
|
||||
],
|
||||
];
|
||||
$data['charts_fields']['field_exposed_chart_type'] = [
|
||||
'title' => t('Exposed Chart Type'),
|
||||
'help' => t('Use this field for exposing chart type.'),
|
||||
'field' => [
|
||||
'id' => 'field_exposed_chart_type',
|
||||
],
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_theme().
|
||||
*/
|
||||
function charts_theme($existing, $type, $theme, $path) {
|
||||
return [
|
||||
'charts_chart' => [
|
||||
'render element' => 'element',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_preprocess_HOOK().
|
||||
*/
|
||||
function template_preprocess_charts_chart(&$variables) {
|
||||
$element = $variables['element'];
|
||||
|
||||
$attributes = $element['#attributes'];
|
||||
$attributes['id'] = $element['#id'];
|
||||
$attributes['class'][] = 'chart';
|
||||
|
||||
$variables['content'] = [
|
||||
'#type' => 'html_tag',
|
||||
'#tag' => 'div',
|
||||
'#attributes' => $attributes,
|
||||
'#value' => $element['#chart'] ?? '',
|
||||
];
|
||||
$variables['content_prefix'] = $element['#content_prefix'];
|
||||
$variables['content_suffix'] = $element['#content_suffix'];
|
||||
$config = \Drupal::config('charts.settings');
|
||||
$variables['debug'] = [];
|
||||
if (!empty($config->get('advanced.debug'))) {
|
||||
$variables['debug'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => t('Chart JSON'),
|
||||
'#open' => FALSE,
|
||||
'#attributes' => [
|
||||
'data-charts-debug-container' => TRUE,
|
||||
],
|
||||
'#collapsible' => TRUE,
|
||||
'json' => [
|
||||
'#prefix' => '<pre class="language-json">',
|
||||
'#type' => 'html_tag',
|
||||
'#tag' => 'code',
|
||||
'#attributes' => [
|
||||
'class' => ['language-json'],
|
||||
],
|
||||
'#value' => t('If this is blank, your library will need to implement code in your JavaScript. Please see the README.md.'),
|
||||
'#suffix' => '</pre>',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_views_pre_view().
|
||||
*/
|
||||
function charts_views_pre_view(ViewExecutable $view, $display_id, array &$args) {
|
||||
$hasFields = array_key_exists('fields', $view->display_handler->options);
|
||||
if ($hasFields) {
|
||||
$fields = $view->display_handler->getOption('fields');
|
||||
$hasViewsFieldsOnOffHandler = FALSE;
|
||||
foreach ($fields as $field) {
|
||||
if (array_key_exists('plugin_id', $field)) {
|
||||
|
||||
if ($field['plugin_id'] == 'field_exposed_chart_type') {
|
||||
$hasViewsFieldsOnOffHandler = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($hasViewsFieldsOnOffHandler) {
|
||||
// Grab the type that has been submitted.
|
||||
$params = \Drupal::request()->query->all();
|
||||
// This is for a GET request.
|
||||
// If the view is submitted through AJAX, like in view preview, it will be
|
||||
// a POST request. Merge the parameter arrays and we will get our values.
|
||||
$postParams = \Drupal::request()->request->all();
|
||||
$params = array_merge($params, $postParams);
|
||||
foreach ($params as $key => $value) {
|
||||
if (strpos($key, 'ct') === 0) {
|
||||
$view->storage->set('exposed_chart_type', $value);
|
||||
}
|
||||
}
|
||||
$view->element['#cache']['contexts'][] = 'url';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_library_info_alter().
|
||||
*
|
||||
* Adapted from webform_library_info_alter() to add CDN support.
|
||||
*/
|
||||
function charts_library_info_alter(&$libraries, $extension) {
|
||||
// Only process for charts_* modules.
|
||||
if (strpos($extension, 'charts_') !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only try to apply cdn changes if the cdn requirements is on.
|
||||
$config = \Drupal::config('charts.settings');
|
||||
if (!$config->get('advanced.requirements.cdn')) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($libraries as &$library) {
|
||||
if (isset($library['cdn']) && is_array($library['cdn'])) {
|
||||
_charts_library_info_alter_recursive($library, $library['cdn']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive through a charts library.
|
||||
*
|
||||
* @param array $library
|
||||
* A library defined in a chart module.
|
||||
* @param array $cdn
|
||||
* A associative array of library paths mapped to CDN URL.
|
||||
*
|
||||
* @note copied from _webform_library_info_alter_recursive().
|
||||
*/
|
||||
function _charts_library_info_alter_recursive(array &$library, array $cdn) {
|
||||
foreach ($library as $key => &$value) {
|
||||
// CSS and JS files and listed in associative arrays keyed via string.
|
||||
if (!is_string($key) || !is_array($value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore the CDN's associative array.
|
||||
if ($key === 'cdn') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Replace the CDN sources (i.e. /library/*) with the CDN URL destination
|
||||
// (https://cdnjs.cloudflare.com/ajax/libs/*).
|
||||
foreach ($cdn as $source => $destination) {
|
||||
if (_charts_library_check_source_exists($source)) {
|
||||
continue;
|
||||
}
|
||||
if (strpos($key, $source) === 0) {
|
||||
$uri = str_replace($source, $destination, $key);
|
||||
$library[$uri] = $value;
|
||||
$library[$uri]['type'] = 'external';
|
||||
unset($library[$key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Recurse downward to find nested libraries.
|
||||
_charts_library_info_alter_recursive($value, $cdn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a library source directory or file exists locally.
|
||||
*
|
||||
* @param string $source
|
||||
* The source directory or file.
|
||||
*
|
||||
* @return bool
|
||||
* True when the file exist, FALSE otherwise.
|
||||
*/
|
||||
function _charts_library_check_source_exists(string $source) {
|
||||
$source = rtrim($source, '/');
|
||||
|
||||
$container = \Drupal::getContainer();
|
||||
$app_root = $container->getParameter('app.root');
|
||||
$site_path = $container->getParameter('site.path');
|
||||
|
||||
// The following logic is taken from libraries_get_libraries()
|
||||
$search_dir = [];
|
||||
$search_dir[] = $app_root;
|
||||
// Similar to 'modules' and 'themes' directories inside an installation
|
||||
// profile, installation profiles may want to place libraries into a
|
||||
// 'libraries' directory.
|
||||
$search_dir[] = 'profiles/' . \Drupal::installProfile();
|
||||
// Also search sites/<domain>/*.
|
||||
$search_dir[] = $site_path;
|
||||
|
||||
foreach ($search_dir as $dir) {
|
||||
if (file_exists($dir . $source) || file_exists($dir . $source)) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
4
web/modules/charts/charts.permissions.yml
Executable file
4
web/modules/charts/charts.permissions.yml
Executable file
@@ -0,0 +1,4 @@
|
||||
'access all charts':
|
||||
title: 'Administer Charts'
|
||||
description: 'This permission needs to be fleshed out.'
|
||||
'restrict access': true
|
||||
221
web/modules/charts/charts.post_update.php
Normal file
221
web/modules/charts/charts.post_update.php
Normal file
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Charts post-update file.
|
||||
*/
|
||||
|
||||
use Drupal\charts\ConfigUpdater;
|
||||
use Drupal\Component\Serialization\Yaml;
|
||||
|
||||
/**
|
||||
* Initialize advanced requirements cdn config value.
|
||||
*/
|
||||
function charts_post_update_initialize_advanced_requirements_cdn(&$sandbox) {
|
||||
$config = \Drupal::service('config.factory')
|
||||
->getEditable('charts.settings');
|
||||
|
||||
if ($config) {
|
||||
$config->set('advanced', ['requirements' => ['cdn' => TRUE]]);
|
||||
$config->save();
|
||||
return 'Successfully initialized the advanced.requirements.cdn option.';
|
||||
}
|
||||
return 'No initialization was done. Please check your current config.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the existing default config display colors to increase from 10 to 25.
|
||||
*/
|
||||
function charts_post_update_existing_default_colors_to_twenty_five(&$sandbox) {
|
||||
$config = \Drupal::service('config.factory')->getEditable('charts.settings');
|
||||
$existing_colors = $config->get('charts_default_settings.display.colors');
|
||||
|
||||
if (is_countable($existing_colors) && count($existing_colors) === 10) {
|
||||
$path = \Drupal::service('extension.list.module')->getPath('charts');
|
||||
$default_install_settings_file = $path . '/config/install/charts.settings.yml';
|
||||
if (!file_exists($default_install_settings_file)) {
|
||||
return 'Failed to update the default colors to 25 because we could not load the charts settings.';
|
||||
}
|
||||
|
||||
$default_install_settings = Yaml::decode(file_get_contents($default_install_settings_file));
|
||||
$install_colors = $default_install_settings['charts_default_settings']['display']['colors'];
|
||||
// We only want to add the last 15 colors to make it 25.
|
||||
$colors = array_merge($existing_colors, array_slice($install_colors, -15));
|
||||
$config->set('charts_default_settings.display.colors', $colors);
|
||||
$config->save();
|
||||
return 'Successfully increased the number of default colors to 25.';
|
||||
}
|
||||
return 'We were unable to load existing colors in order to increased them to 25.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the config value for the debug setting.
|
||||
*/
|
||||
function charts_post_update_initialize_debug_option(&$sandbox) {
|
||||
$config = \Drupal::service('config.factory')
|
||||
->getEditable('charts.settings');
|
||||
|
||||
if ($config) {
|
||||
$config->set('advanced.debug', FALSE);
|
||||
$config->save();
|
||||
return 'Successfully initialized the advanced.debug option.';
|
||||
}
|
||||
return 'No initialization was done. Please check your current config.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate library default settings to library_config key.
|
||||
*/
|
||||
function charts_post_update_migrate_library_default_settings_to_library_config(&$sandbox) {
|
||||
/** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */
|
||||
$config_factory = \Drupal::service('config.factory');
|
||||
$config = $config_factory->getEditable('charts.settings');
|
||||
|
||||
if ($config) {
|
||||
$default_settings = $config->get('charts_default_settings');
|
||||
$library_id = $default_settings['library'] ?? '';
|
||||
$old_key = $library_id ? 'charts_default_settings.' . $library_id . '_settings' : '';
|
||||
if ($old_key && empty($default_settings['display']) && $library_config = $config->get($old_key)) {
|
||||
$config->set('charts_default_settings.library_config', $library_config);
|
||||
$config->clear($old_key);
|
||||
$config->save();
|
||||
return 'Library default settings have been successfully migrated to library_config key.';
|
||||
}
|
||||
}
|
||||
return 'No migration was done';
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear library_config set on the wrong place. See drupal.org/i/3310145.
|
||||
*/
|
||||
function charts_post_update_clear_library_config_key_added_on_wrong_place(&$sandbox) {
|
||||
/** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */
|
||||
$config_factory = \Drupal::service('config.factory');
|
||||
$config = $config_factory->getEditable('charts.settings');
|
||||
if ($config && $config->get('library_config')) {
|
||||
// Not to be confused with "charts_default_settings.library_config" which is
|
||||
// the proper key.
|
||||
$config->clear('library_config');
|
||||
$config->save();
|
||||
return 'Successfully cleared the library_config key.';
|
||||
}
|
||||
return 'The library_config key was not cleared, probably because it was not set.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates charts config and views with charts style of version 3 to new config.
|
||||
*/
|
||||
function charts_post_update_00_update_charts_version_3_to_latest_settings_structure(&$sandbox) {
|
||||
/** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */
|
||||
$config_factory = \Drupal::service('config.factory');
|
||||
$config = $config_factory->getEditable('charts.settings');
|
||||
$default_settings = $config ? $config->get('charts_default_settings') : [];
|
||||
|
||||
/** @var \Drupal\charts\ConfigUpdater $config_updater */
|
||||
$config_updater = \Drupal::classResolver(ConfigUpdater::class);
|
||||
if ($default_settings && !isset($default_settings['display'])) {
|
||||
$results = [];
|
||||
|
||||
$config->set('charts_default_settings', $config_updater->transformVersion3SettingsToNew($default_settings));
|
||||
$config->save();
|
||||
$results[] = 'Default Config: Migration to the new structure successfully done.';
|
||||
|
||||
$results[] = $config_updater->updateExistingViewsVersion3ToNewSettings();
|
||||
return implode(' -- ', $results);
|
||||
}
|
||||
elseif (!$default_settings || !$config->getRawData()) {
|
||||
$config->setData($config_updater->initializedCurrentDefaultSettings());
|
||||
$config->save();
|
||||
|
||||
$results = [];
|
||||
$results[] = 'Default Config: No config were found and the default one was saved.';
|
||||
$results[] = $config_updater->updateExistingViewsVersion3ToNewSettings();
|
||||
return implode(' -- ', $results);
|
||||
}
|
||||
return 'No update of the settings structure was done.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-save views that are using the chart style to recalculate dependencies.
|
||||
*/
|
||||
function charts_post_update_resave_views_to_account_calculated_library_dependencies(&$sandbox) {
|
||||
$view_storage = \Drupal::entityTypeManager()->getStorage('view');
|
||||
$view_ids = $view_storage->getQuery()
|
||||
->accessCheck(FALSE)
|
||||
->condition('display.*.display_options.style.type', 'chart', '=')
|
||||
->execute();
|
||||
$labels = [];
|
||||
if ($view_ids) {
|
||||
foreach ($view_ids as $view_id) {
|
||||
/** @var \Drupal\views\ViewEntityInterface $view */
|
||||
$view = $view_storage->load($view_id);
|
||||
// Re-save the view to update its dependencies.
|
||||
$view->save();
|
||||
$labels[] = $view->label();
|
||||
}
|
||||
}
|
||||
|
||||
if ($labels) {
|
||||
return t('The following views have been re-saved to re-calculate their dependencies: @views', [
|
||||
'@views' => implode(',', $labels),
|
||||
]);
|
||||
}
|
||||
return t('This site did not have any chart related views.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting charts config module dependencies based on the selected library.
|
||||
*/
|
||||
function charts_post_update_setting_charts_config_module_dependencies(&$sandbox) {
|
||||
$config = \Drupal::service('config.factory')
|
||||
->getEditable('charts.settings');
|
||||
$settings = $config->get('charts_default_settings');
|
||||
$library = $settings['library'] ?? '';
|
||||
$type = $settings['type'] ?? '';
|
||||
if ($library || $type) {
|
||||
/** @var \Drupal\charts\EventSubscriber\ConfigImportSubscriber $config_import_subscriber */
|
||||
$config_import_subscriber = \Drupal::service('charts.config_import_subscriber');
|
||||
$config->set('dependencies', $config_import_subscriber->calculateDependencies($library, $type))
|
||||
->save();
|
||||
return 'Updated chart settings dependencies';
|
||||
}
|
||||
return 'The default config for charts did not have a library and type set. So no dependencies were set.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Move color changer from fields to display.
|
||||
*/
|
||||
function charts_post_update_move_views_style_color_changer_from_fields_to_display(&$sandbox) {
|
||||
$view_storage = \Drupal::entityTypeManager()->getStorage('view');
|
||||
$view_ids = $view_storage->getQuery()
|
||||
->accessCheck(FALSE)
|
||||
->condition('display.*.display_options.style.type', 'chart', '=')
|
||||
->execute();
|
||||
$labels = [];
|
||||
if ($view_ids) {
|
||||
foreach ($view_ids as $view_id) {
|
||||
/** @var \Drupal\views\ViewEntityInterface $view */
|
||||
$view = $view_storage->load($view_id);
|
||||
$changed = FALSE;
|
||||
foreach (array_keys($view->get('display')) as $display_id) {
|
||||
$display = &$view->getDisplay($display_id);
|
||||
if (isset($display['display_options']['style']['options']['chart_settings']['fields']['color_changer'])) {
|
||||
$changed = TRUE;
|
||||
$display['display_options']['style']['options']['chart_settings']['display']['color_changer'] = !empty($display['display_options']['style']['options']['chart_settings']['fields']['color_changer']);
|
||||
unset($display['display_options']['style']['options']['chart_settings']['fields']['color_changer']);
|
||||
}
|
||||
}
|
||||
if ($changed) {
|
||||
$view->save();
|
||||
$labels[] = $view->label();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($labels) {
|
||||
return t('The following views have been updated to move the color changer from fields to display: @views', [
|
||||
'@views' => implode(',', $labels),
|
||||
]);
|
||||
}
|
||||
return t('This site did not have any chart related views.');
|
||||
}
|
||||
15
web/modules/charts/charts.routing.yml
Executable file
15
web/modules/charts/charts.routing.yml
Executable file
@@ -0,0 +1,15 @@
|
||||
charts.settings:
|
||||
path: /admin/config/content/charts
|
||||
defaults:
|
||||
_form: Drupal\charts\Form\ChartsConfigForm
|
||||
_title: 'Chart configuration'
|
||||
requirements:
|
||||
_permission: 'administer site configuration'
|
||||
|
||||
charts.settings.advanced:
|
||||
path: /admin/config/content/charts/advanced
|
||||
defaults:
|
||||
_form: Drupal\charts\Form\ChartsConfigAdvancedForm
|
||||
_title: 'Chart advanced configuration form'
|
||||
requirements:
|
||||
_permission: 'administer site configuration'
|
||||
21
web/modules/charts/charts.services.yml
Executable file
21
web/modules/charts/charts.services.yml
Executable file
@@ -0,0 +1,21 @@
|
||||
services:
|
||||
plugin.manager.charts:
|
||||
class: Drupal\charts\ChartManager
|
||||
parent: default_plugin_manager
|
||||
|
||||
plugin.manager.charts_type:
|
||||
class: Drupal\charts\TypeManager
|
||||
arguments: ['@module_handler', '@cache.discovery', '@event_dispatcher']
|
||||
|
||||
charts.plugins_uninstall_validator:
|
||||
class: Drupal\charts\PluginsUninstallValidator
|
||||
tags:
|
||||
- { name: module_install.uninstall_validator }
|
||||
arguments: ['@config.factory']
|
||||
lazy: true
|
||||
|
||||
charts.config_import_subscriber:
|
||||
class: Drupal\charts\EventSubscriber\ConfigImportSubscriber
|
||||
arguments: ['@plugin.manager.charts', '@plugin.manager.charts_type']
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
0
web/modules/charts/charts.settings.yml
Executable file
0
web/modules/charts/charts.settings.yml
Executable file
73
web/modules/charts/config/install/charts.settings.yml
Normal file
73
web/modules/charts/config/install/charts.settings.yml
Normal file
@@ -0,0 +1,73 @@
|
||||
dependencies: []
|
||||
charts_default_settings:
|
||||
library: ''
|
||||
type: line
|
||||
display:
|
||||
title: ''
|
||||
subtitle: ''
|
||||
colors:
|
||||
- '#006fb0'
|
||||
- '#f07c33'
|
||||
- '#342e9c'
|
||||
- '#579b17'
|
||||
- '#3f067a'
|
||||
- '#cbde67'
|
||||
- '#7643b6'
|
||||
- '#738d00'
|
||||
- '#c157c7'
|
||||
- '#02dab1'
|
||||
- '#ed56b4'
|
||||
- '#d8d981'
|
||||
- '#004695'
|
||||
- '#736000'
|
||||
- '#a5a5ff'
|
||||
- '#833a00'
|
||||
- '#ff9ee9'
|
||||
- '#684507'
|
||||
- '#fe4f85'
|
||||
- '#5d0011'
|
||||
- '#ffa67b'
|
||||
- '#88005c'
|
||||
- '#ff9b8f'
|
||||
- '#85000f'
|
||||
- '#ff7581'
|
||||
color_changer: false
|
||||
title_position: out
|
||||
tooltips: true
|
||||
tooltips_use_html: false
|
||||
data_markers: false
|
||||
data_labels: false
|
||||
legend: false
|
||||
legend_position: right
|
||||
background: ''
|
||||
three_dimensional: 0
|
||||
polar: 0
|
||||
dimensions:
|
||||
width: ''
|
||||
width_units: '%'
|
||||
height: ''
|
||||
height_units: ''
|
||||
gauge:
|
||||
max: '100'
|
||||
min: '0'
|
||||
green_from: '85'
|
||||
green_to: '100'
|
||||
yellow_from: '50'
|
||||
yellow_to: '85'
|
||||
red_from: '0'
|
||||
red_to: '50'
|
||||
xaxis:
|
||||
title: ''
|
||||
labels_rotation: '0'
|
||||
yaxis:
|
||||
title: ''
|
||||
min: ''
|
||||
max: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
decimal_count: ''
|
||||
labels_rotation: '0'
|
||||
advanced:
|
||||
debug: false
|
||||
requirements:
|
||||
cdn: true
|
||||
216
web/modules/charts/config/schema/charts.data_types.schema.yml
Normal file
216
web/modules/charts/config/schema/charts.data_types.schema.yml
Normal file
@@ -0,0 +1,216 @@
|
||||
# Basic data types for charts.
|
||||
|
||||
charts_dimensions:
|
||||
type: mapping
|
||||
mapping:
|
||||
width:
|
||||
type: string
|
||||
label: 'Width'
|
||||
width_units:
|
||||
type: string
|
||||
label: 'Width units'
|
||||
height:
|
||||
type: string
|
||||
label: 'Height'
|
||||
height_units:
|
||||
type: string
|
||||
label: 'Height units'
|
||||
|
||||
charts_gauge:
|
||||
type: mapping
|
||||
mapping:
|
||||
max:
|
||||
type: string
|
||||
label: 'Gauge maximum value'
|
||||
min:
|
||||
type: string
|
||||
label: 'Gauge minimum value'
|
||||
green_from:
|
||||
type: string
|
||||
label: 'Green minimum value'
|
||||
green_to:
|
||||
type: string
|
||||
label: 'Green maximum value'
|
||||
yellow_from:
|
||||
type: string
|
||||
label: 'Yellow minimum value'
|
||||
yellow_to:
|
||||
type: string
|
||||
label: 'Yellow maximum value'
|
||||
red_from:
|
||||
type: string
|
||||
label: 'Red minimum value'
|
||||
red_to:
|
||||
type: string
|
||||
label: 'Red maximum value'
|
||||
|
||||
charts_display:
|
||||
type: mapping
|
||||
mapping:
|
||||
title:
|
||||
type: label
|
||||
label: 'Chart title'
|
||||
title_position:
|
||||
type: string
|
||||
label: 'Title position'
|
||||
subtitle:
|
||||
type: string
|
||||
label: 'Chart subtitle'
|
||||
data_labels:
|
||||
type: boolean
|
||||
label: 'Enable data labels'
|
||||
data_markers:
|
||||
type: boolean
|
||||
label: 'Enable data markers'
|
||||
legend:
|
||||
type: boolean
|
||||
label: 'Legend'
|
||||
legend_position:
|
||||
type: string
|
||||
label: 'Legend position'
|
||||
background:
|
||||
type: string
|
||||
label: 'Background'
|
||||
three_dimensional:
|
||||
type: integer
|
||||
label: 'Make chart three-dimensional (3D)'
|
||||
colors:
|
||||
type: sequence
|
||||
label: 'Chart colors'
|
||||
sequence:
|
||||
type: string
|
||||
label: 'Hexadecimal color'
|
||||
polar:
|
||||
type: integer
|
||||
label: 'Transform cartesian charts into the polar coordinate system'
|
||||
tooltips:
|
||||
type: boolean
|
||||
label: 'Tooltips'
|
||||
tooltips_use_html:
|
||||
type: boolean
|
||||
label: 'Tooltips Use HTML'
|
||||
dimensions:
|
||||
type: charts_dimensions
|
||||
label: 'Dimensions'
|
||||
gauge:
|
||||
type: charts_gauge
|
||||
label: 'Gauge'
|
||||
color_changer:
|
||||
type: boolean
|
||||
label: 'Enable color changer widget'
|
||||
|
||||
charts_xaxis:
|
||||
type: mapping
|
||||
mapping:
|
||||
title:
|
||||
type: label
|
||||
label: 'X axis title'
|
||||
labels_rotation:
|
||||
type: string
|
||||
label: 'X axis labels rotation'
|
||||
|
||||
charts_yaxis:
|
||||
type: mapping
|
||||
mapping:
|
||||
title:
|
||||
type: label
|
||||
label: 'Y axis title'
|
||||
min:
|
||||
type: string
|
||||
label: 'Y axis min value'
|
||||
max:
|
||||
type: string
|
||||
label: 'Y axis max value'
|
||||
prefix:
|
||||
type: label
|
||||
label: 'Y axis prefix'
|
||||
suffix:
|
||||
type: label
|
||||
label: 'Y axis suffix'
|
||||
decimal_count:
|
||||
type: string
|
||||
label: 'Y axis decimal count'
|
||||
labels_rotation:
|
||||
type: string
|
||||
label: 'Y axis labels rotation'
|
||||
|
||||
charts_views_field_data_provider:
|
||||
type: mapping
|
||||
mapping:
|
||||
enabled:
|
||||
type: boolean
|
||||
label: 'Enabled'
|
||||
color:
|
||||
type: string
|
||||
label: Color
|
||||
weight:
|
||||
type: integer
|
||||
label: 'Weight'
|
||||
|
||||
charts_views_field_entity_grouping_selected_method_colors:
|
||||
type: mapping
|
||||
mapping:
|
||||
color:
|
||||
type: string
|
||||
label: Color
|
||||
|
||||
charts_config:
|
||||
type: mapping
|
||||
mapping:
|
||||
library:
|
||||
type: string
|
||||
label: 'Charting library'
|
||||
type:
|
||||
type: string
|
||||
label: 'Chart type'
|
||||
fields:
|
||||
type: mapping
|
||||
label: 'Charts fields'
|
||||
mapping:
|
||||
label:
|
||||
type: string
|
||||
label: 'Label field'
|
||||
stacking:
|
||||
type: boolean
|
||||
label: Stacking
|
||||
data_providers:
|
||||
type: sequence
|
||||
label: 'Data providers'
|
||||
sequence:
|
||||
type: charts_views_field_data_provider
|
||||
entity_grouping:
|
||||
type: mapping
|
||||
label: 'Entity grouping settings'
|
||||
mapping:
|
||||
color_selection_method:
|
||||
type: string
|
||||
label: 'Color selection method'
|
||||
selected_method:
|
||||
type: mapping
|
||||
label: 'Selected method'
|
||||
mapping:
|
||||
color_field_name:
|
||||
type: string
|
||||
label: 'Color field name'
|
||||
colors:
|
||||
type: sequence
|
||||
label: 'Colors'
|
||||
sequence:
|
||||
type: charts_views_field_entity_grouping_selected_method_colors
|
||||
display:
|
||||
type: charts_display
|
||||
label: 'Display'
|
||||
xaxis:
|
||||
type: charts_yaxis
|
||||
label: 'X axis Display'
|
||||
yaxis:
|
||||
type: charts_yaxis
|
||||
label: 'Y axis Display'
|
||||
library_config:
|
||||
type: charts.settings.library_plugin.[%parent.library]
|
||||
|
||||
charts.settings.library_plugin.*:
|
||||
type: charts_settings_library_plugin
|
||||
|
||||
charts_settings_library_plugin:
|
||||
type: mapping
|
||||
24
web/modules/charts/config/schema/charts.settings.schema.yml
Executable file
24
web/modules/charts/config/schema/charts.settings.schema.yml
Executable file
@@ -0,0 +1,24 @@
|
||||
charts.settings:
|
||||
type: config_object
|
||||
label: 'Charts settings'
|
||||
mapping:
|
||||
charts_default_settings:
|
||||
type: charts_config
|
||||
label: 'Charts default settings'
|
||||
advanced:
|
||||
type: mapping
|
||||
label: Advanced
|
||||
mapping:
|
||||
debug:
|
||||
type: boolean
|
||||
label: 'Enable Charts Debug'
|
||||
requirements:
|
||||
type: mapping
|
||||
label: Requirements
|
||||
mapping:
|
||||
cdn:
|
||||
type: boolean
|
||||
label: 'Check if CDN is being used for external libraries'
|
||||
dependencies:
|
||||
type: config_dependencies
|
||||
label: 'Dependencies'
|
||||
47
web/modules/charts/config/schema/charts.views.schema.yml
Executable file
47
web/modules/charts/config/schema/charts.views.schema.yml
Executable file
@@ -0,0 +1,47 @@
|
||||
views.style.chart:
|
||||
type: views_style
|
||||
label: 'Chart'
|
||||
mapping:
|
||||
chart_settings:
|
||||
type: charts_config
|
||||
label: 'Settings'
|
||||
|
||||
views.display.chart_extension:
|
||||
type: views_display
|
||||
label: 'Attachment display options'
|
||||
|
||||
views.field.field_exposed_chart_type:
|
||||
type: views_field
|
||||
label: 'Exposed Chart Type'
|
||||
mapping:
|
||||
chart_types:
|
||||
type: sequence
|
||||
label: 'Chart Type Options'
|
||||
exposed_select_type:
|
||||
type: string
|
||||
label: 'Exposed Selection Type'
|
||||
|
||||
views.field.field_charts_fields_bubble:
|
||||
type: views_field
|
||||
label: 'Bubble Field'
|
||||
mapping:
|
||||
x_axis:
|
||||
type: string
|
||||
label: 'X Axis Field'
|
||||
y_axis:
|
||||
type: string
|
||||
label: 'Y Axis Field'
|
||||
z_axis:
|
||||
type: string
|
||||
label: 'Z Axis Field'
|
||||
|
||||
views.field.field_charts_fields_scatter:
|
||||
type: views_field
|
||||
label: 'Scatter Field'
|
||||
mapping:
|
||||
x_axis:
|
||||
type: string
|
||||
label: 'X Axis Field'
|
||||
y_axis:
|
||||
type: string
|
||||
label: 'Y Axis Field'
|
||||
46
web/modules/charts/js/charts.es6.js
Normal file
46
web/modules/charts/js/charts.es6.js
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* @file
|
||||
* Charts API.
|
||||
*/
|
||||
((Drupal) => {
|
||||
|
||||
Drupal.Charts = Drupal.Charts || {};
|
||||
Drupal.Charts.Configs = Drupal.Charts.Configs || [];
|
||||
|
||||
/**
|
||||
* @typedef {class} Drupal.Charts.Contents
|
||||
*/
|
||||
|
||||
Drupal.Charts.Contents = class {
|
||||
constructor() {
|
||||
const chartsElements = document.querySelectorAll('[data-chart]');
|
||||
chartsElements.forEach(function (el) {
|
||||
const id = el.getAttribute('id');
|
||||
Drupal.Charts.Configs[id] = JSON.parse(el.getAttribute('data-chart'));
|
||||
Drupal.Charts.Configs[id].drupalChartDivElement = el;
|
||||
Drupal.Charts.Configs[id].drupalChartDivId = id;
|
||||
});
|
||||
}
|
||||
|
||||
initialize(id) {
|
||||
const event = new CustomEvent('drupalChartsConfigsInitialization', {
|
||||
detail: Drupal.Charts.Configs[id]
|
||||
});
|
||||
Drupal.Charts.Configs[id].drupalChartDivElement.dispatchEvent(event);
|
||||
}
|
||||
|
||||
static update(id, data) {
|
||||
if (Drupal.Charts.Configs.hasOwnProperty(id)) {
|
||||
Drupal.Charts.Configs[id] = data;
|
||||
}
|
||||
}
|
||||
|
||||
getData(id) {
|
||||
if (Drupal.Charts.Configs.hasOwnProperty(id)) {
|
||||
this.initialize(id);
|
||||
return Drupal.Charts.Configs[id];
|
||||
}
|
||||
return {};
|
||||
}
|
||||
};
|
||||
})(Drupal);
|
||||
45
web/modules/charts/js/charts.js
Normal file
45
web/modules/charts/js/charts.js
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(Drupal => {
|
||||
Drupal.Charts = Drupal.Charts || {};
|
||||
Drupal.Charts.Configs = Drupal.Charts.Configs || [];
|
||||
Drupal.Charts.Contents = class {
|
||||
constructor() {
|
||||
const charts_elements = document.querySelectorAll('[data-chart]');
|
||||
charts_elements.forEach(function (el) {
|
||||
const id = el.getAttribute('id');
|
||||
Drupal.Charts.Configs[id] = JSON.parse(el.getAttribute('data-chart'));
|
||||
Drupal.Charts.Configs[id].drupalChartDivElement = el;
|
||||
Drupal.Charts.Configs[id].drupalChartDivId = id;
|
||||
});
|
||||
}
|
||||
|
||||
initialize(id) {
|
||||
const event = new CustomEvent('drupalChartsConfigsInitialization', {
|
||||
detail: Drupal.Charts.Configs[id]
|
||||
});
|
||||
Drupal.Charts.Configs[id].drupalChartDivElement.dispatchEvent(event);
|
||||
}
|
||||
|
||||
static update(id, data) {
|
||||
if (Drupal.Charts.Configs.hasOwnProperty(id)) {
|
||||
Drupal.Charts.Configs[id] = data;
|
||||
}
|
||||
}
|
||||
|
||||
getData(id) {
|
||||
if (Drupal.Charts.Configs.hasOwnProperty(id)) {
|
||||
this.initialize(id);
|
||||
return Drupal.Charts.Configs[id];
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
};
|
||||
})(Drupal);
|
||||
15
web/modules/charts/modules/charts_api_example/README.md
Executable file
15
web/modules/charts/modules/charts_api_example/README.md
Executable file
@@ -0,0 +1,15 @@
|
||||
#Charts API Example
|
||||
|
||||
This module shows you how to create charts using render arrays. There are
|
||||
numerous examples in the `ChartsApiExample.php` controller file. In the
|
||||
`charts_api_example.module` file, you can also see how some hooks described in
|
||||
`charts.api.php` can be used. One hook overrides a chart using PHP. Another
|
||||
overrides with JavaScript - this is necessary in some cases when the charting
|
||||
library API is expecting a function rather than a string. You can read more
|
||||
about overriding with JavaScript here:
|
||||
https://www.drupal.org/project/charts/issues/3197574#comment-13999765
|
||||
|
||||
To see the examples in your site, navigate to: `/charts/example/display`
|
||||
|
||||
Settings for these charts are set in the Charts configuration page
|
||||
(`/admin/config/content/charts`).
|
||||
@@ -0,0 +1,12 @@
|
||||
name: 'Charts API Example'
|
||||
type: module
|
||||
description: 'Demonstrates how to use the Charts module without Views.'
|
||||
core_version_requirement: ^10.3 || ^11
|
||||
package: Examples
|
||||
dependencies:
|
||||
- 'charts:charts'
|
||||
|
||||
# Information added by Drupal.org packaging script on 2024-12-02
|
||||
version: '5.1.1'
|
||||
project: 'charts'
|
||||
datestamp: 1733170633
|
||||
@@ -0,0 +1,6 @@
|
||||
charts_api_example:
|
||||
version: VERSION
|
||||
js:
|
||||
js/charts_api_example_js_override.js: { weight: -5 }
|
||||
dependencies:
|
||||
- charts/global
|
||||
@@ -0,0 +1,5 @@
|
||||
charts_api_example.display:
|
||||
title: 'Charts API Example'
|
||||
description: 'Displays charts rendered using the Charts API.'
|
||||
parent: system.admin_config_content
|
||||
route_name: charts_api_example.display
|
||||
43
web/modules/charts/modules/charts_api_example/charts_api_example.module
Executable file
43
web/modules/charts/modules/charts_api_example/charts_api_example.module
Executable file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Charts API Example module file.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function charts_api_example_help($route_name, RouteMatchInterface $route_match) {
|
||||
$output = '';
|
||||
switch ($route_name) {
|
||||
// Main module help for the charts_api_example module.
|
||||
case 'help.page.charts_api_example':
|
||||
$output .= '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>' . t('A simple example on how to interact with the Charts API') . '</p>';
|
||||
break;
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_chart_definition_CHART_ID_alter().
|
||||
*/
|
||||
function charts_api_example_chart_definition_exampleidphp_alter(&$chart) {
|
||||
// Please note: this will only show when the charts_highcharts module is
|
||||
// enabled and Highcharts is set as your default library.
|
||||
$chart['chart']['backgroundColor'] = 'blue';
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_chart_alter().
|
||||
*/
|
||||
function charts_api_example_chart_alter(array &$element, $chart_id) {
|
||||
// Please note: this will only show when the charts_highcharts module is
|
||||
// enabled and Highcharts is set as your default library.
|
||||
if ($chart_id === 'exampleidjschart') {
|
||||
$element['#attached']['library'][] = 'charts_api_example/charts_api_example';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
charts_api_example.display:
|
||||
path: /charts/example/display
|
||||
defaults:
|
||||
_controller: '\Drupal\charts_api_example\Controller\ChartsApiExample::display'
|
||||
_title: 'How to Use the Charts API'
|
||||
requirements:
|
||||
_permission: 'access content'
|
||||
16
web/modules/charts/modules/charts_api_example/composer.json
Executable file
16
web/modules/charts/modules/charts_api_example/composer.json
Executable file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "drupal/charts_api_example",
|
||||
"type": "drupal-module",
|
||||
"description": "How to interact with the Charts Api",
|
||||
"keywords": [
|
||||
"Drupal"
|
||||
],
|
||||
"license": "GPL-2.0+",
|
||||
"homepage": "https://www.drupal.org/project/charts_api_example",
|
||||
"minimum-stability": "dev",
|
||||
"support": {
|
||||
"issues": "https://www.drupal.org/project/issues/charts_api_example",
|
||||
"source": "http://cgit.drupalcode.org/charts_api_example"
|
||||
},
|
||||
"require": {}
|
||||
}
|
||||
@@ -0,0 +1,463 @@
|
||||
Week,6.x-0.x,6.x-1.x,7.x-1.x,7.x-2.x,8.x-1.x,8.x-3.x,8.x-4.x,5.0.x,Total
|
||||
21-Aug-21,0,71,12,13458,1,4632,6,637,18817
|
||||
14-Aug-21,0,77,13,13517,1,4518,9,608,18743
|
||||
7-Aug-21,0,73,11,13586,1,4651,6,606,18934
|
||||
31-Jul-21,0,76,13,13479,1,4488,7,619,18683
|
||||
24-Jul-21,0,70,10,13771,1,4385,7,530,18774
|
||||
17-Jul-21,0,73,12,13756,1,4430,8,536,18816
|
||||
10-Jul-21,0,77,13,13596,1,4362,8,519,18576
|
||||
3-Jul-21,0,75,11,13715,1,4437,8,596,18843
|
||||
26-Jun-21,0,73,12,13925,1,4470,9,651,19141
|
||||
19-Jun-21,0,80,13,13886,1,4583,7,499,19069
|
||||
12-Jun-21,0,79,13,13651,1,4506,9,455,18714
|
||||
5-Jun-21,0,84,12,13656,1,4525,8,457,18743
|
||||
29-May-21,0,79,15,13935,1,4554,10,438,19032
|
||||
22-May-21,0,84,14,13840,1,4511,8,500,18958
|
||||
15-May-21,0,80,13,13790,2,4524,11,553,18973
|
||||
8-May-21,0,85,13,13748,1,4448,9,357,18661
|
||||
1-May-21,0,83,12,13853,0,4371,8,379,18706
|
||||
24-Apr-21,0,82,14,13835,1,4431,6,340,18709
|
||||
17-Apr-21,0,86,14,14080,0,4455,10,322,18967
|
||||
10-Apr-21,0,82,12,13736,0,4326,8,280,18444
|
||||
3-Apr-21,0,82,14,13815,0,4347,8,289,18555
|
||||
27-Mar-21,0,78,12,13490,1,4212,7,325,18125
|
||||
20-Mar-21,0,77,12,13667,0,4389,9,366,18520
|
||||
13-Mar-21,0,77,12,13671,0,4293,9,269,18331
|
||||
6-Mar-21,0,75,14,13840,0,4361,8,253,18551
|
||||
27-Feb-21,0,81,14,14144,1,4413,9,235,18897
|
||||
20-Feb-21,0,83,14,14151,0,4451,10,217,18926
|
||||
13-Feb-21,0,80,13,14057,0,4294,10,247,18701
|
||||
6-Feb-21,0,86,16,14042,0,4271,10,233,18658
|
||||
30-Jan-21,0,78,13,14221,0,4330,7,257,18906
|
||||
23-Jan-21,0,85,13,14109,0,4283,9,192,18691
|
||||
16-Jan-21,0,94,16,14294,0,4227,9,162,18802
|
||||
9-Jan-21,0,88,15,14244,0,4133,9,154,18643
|
||||
2-Jan-21,0,81,14,14235,0,4123,15,145,18613
|
||||
26-Dec-20,0,82,15,13746,0,3721,9,130,17703
|
||||
19-Dec-20,0,83,15,13904,0,3704,9,128,17843
|
||||
12-Dec-20,0,86,16,14299,0,4007,16,170,18594
|
||||
5-Dec-20,0,85,15,14714,0,4047,12,152,19025
|
||||
28-Nov-20,0,81,14,14852,0,3970,16,148,19081
|
||||
21-Nov-20,0,78,16,14817,0,4347,17,108,19383
|
||||
14-Nov-20,0,86,17,14840,0,4567,22,62,19594
|
||||
7-Nov-20,0,87,18,14286,0,4110,18,80,18599
|
||||
31-Oct-20,0,86,19,14599,0,4100,25,49,18878
|
||||
24-Oct-20,0,91,16,14571,0,4157,25,3,18863
|
||||
17-Oct-20,0,84,16,14434,0,5116,25,0,19675
|
||||
10-Oct-20,0,87,15,14563,0,5102,33,0,19800
|
||||
3-Oct-20,0,83,18,14593,0,4059,26,0,18779
|
||||
26-Sep-20,0,88,17,14717,0,3971,23,0,18816
|
||||
19-Sep-20,0,91,20,14605,0,4064,22,0,18802
|
||||
12-Sep-20,0,87,19,14706,0,4865,24,0,19701
|
||||
5-Sep-20,0,88,20,14661,0,4641,29,0,19439
|
||||
29-Aug-20,0,91,19,14814,0,4557,28,0,19509
|
||||
22-Aug-20,0,92,17,14570,0,4572,30,0,19281
|
||||
15-Aug-20,0,84,20,14743,0,4615,32,0,19494
|
||||
8-Aug-20,0,92,20,14800,0,4627,28,0,19567
|
||||
1-Aug-20,0,90,19,14925,0,4433,20,0,19487
|
||||
25-Jul-20,0,92,19,15013,0,4533,19,0,19676
|
||||
18-Jul-20,0,89,20,14750,0,4532,22,0,19413
|
||||
11-Jul-20,0,93,15,14901,0,4594,20,0,19623
|
||||
4-Jul-20,0,88,21,15080,0,4467,26,0,19682
|
||||
27-Jun-20,0,93,19,15281,0,4679,26,0,20098
|
||||
20-Jun-20,0,86,21,15642,0,4835,25,0,20609
|
||||
13-Jun-20,0,91,25,15979,0,4613,23,0,20731
|
||||
6-Jun-20,0,92,23,15597,0,4564,20,0,20296
|
||||
30-May-20,0,86,25,15812,0,4433,17,0,20373
|
||||
23-May-20,0,94,27,15830,0,4163,15,0,20129
|
||||
16-May-20,0,98,29,15739,0,4169,14,0,20049
|
||||
9-May-20,0,98,23,15300,0,4336,12,0,19769
|
||||
2-May-20,0,94,27,15370,0,4261,12,0,19764
|
||||
25-Apr-20,0,99,28,14711,0,4043,9,0,18890
|
||||
18-Apr-20,0,102,25,14857,1,3970,4,0,18959
|
||||
11-Apr-20,0,107,27,14884,0,3966,4,0,18988
|
||||
4-Apr-20,0,104,25,14829,0,3928,3,0,18889
|
||||
28-Mar-20,0,101,25,15302,0,3937,4,0,19369
|
||||
21-Mar-20,0,103,27,15217,1,4067,3,0,19418
|
||||
14-Mar-20,0,107,26,15136,0,3813,1,0,19083
|
||||
7-Mar-20,0,106,26,15202,0,3797,0,0,19131
|
||||
29-Feb-20,0,106,29,15269,1,3795,0,0,19200
|
||||
22-Feb-20,0,106,30,15222,0,3726,0,0,19084
|
||||
15-Feb-20,0,111,29,15289,0,3596,1,0,19026
|
||||
8-Feb-20,0,108,28,15328,0,3667,0,0,19131
|
||||
1-Feb-20,0,110,28,15523,1,3673,0,0,19335
|
||||
25-Jan-20,0,114,25,15279,0,3523,0,0,18941
|
||||
18-Jan-20,0,108,29,15289,0,3549,0,0,18975
|
||||
11-Jan-20,0,115,29,15540,1,3561,0,0,19246
|
||||
4-Jan-20,0,123,26,15612,0,3492,0,0,19253
|
||||
28-Dec-19,0,117,30,15151,0,3065,0,0,18363
|
||||
21-Dec-19,0,122,26,15067,0,3179,0,0,18394
|
||||
14-Dec-19,0,125,27,16078,1,3614,0,0,19845
|
||||
7-Dec-19,0,124,25,16029,0,3634,0,0,19812
|
||||
30-Nov-19,0,127,30,15804,0,3591,1,0,19553
|
||||
23-Nov-19,0,119,28,15640,0,3512,2,0,19301
|
||||
16-Nov-19,0,119,31,15755,1,3659,1,0,19566
|
||||
9-Nov-19,0,129,29,15583,0,3449,3,0,19193
|
||||
2-Nov-19,0,124,32,15773,0,3488,3,0,19420
|
||||
26-Oct-19,0,138,29,15742,0,3345,0,0,19254
|
||||
19-Oct-19,0,129,27,15927,1,3347,0,0,19431
|
||||
12-Oct-19,0,127,31,15761,0,3414,1,0,19334
|
||||
5-Oct-19,0,143,38,15753,0,3405,1,0,19340
|
||||
28-Sep-19,0,139,30,15852,1,3428,1,0,19451
|
||||
21-Sep-19,0,129,29,15753,0,3194,0,0,19105
|
||||
14-Sep-19,0,131,30,15994,0,3107,0,0,19262
|
||||
7-Sep-19,0,134,30,15560,0,3222,0,0,18946
|
||||
31-Aug-19,0,133,35,15642,1,3279,0,0,19090
|
||||
24-Aug-19,0,131,33,15477,0,3163,0,0,18804
|
||||
17-Aug-19,0,145,34,15513,0,3095,0,0,18787
|
||||
10-Aug-19,0,144,29,15331,1,2854,0,0,18359
|
||||
3-Aug-19,0,150,30,15495,0,2924,0,0,18599
|
||||
27-Jul-19,0,135,32,15766,0,2942,0,0,18875
|
||||
20-Jul-19,0,145,35,15715,1,2864,0,0,18760
|
||||
13-Jul-19,0,141,29,15700,0,2975,0,0,18845
|
||||
6-Jul-19,0,140,32,15572,0,3223,0,0,18967
|
||||
29-Jun-19,0,153,32,15719,1,3087,0,0,18992
|
||||
22-Jun-19,0,151,33,15604,1,3045,0,0,18834
|
||||
15-Jun-19,0,147,35,15576,0,3226,0,0,18984
|
||||
8-Jun-19,0,150,29,15515,0,2942,0,0,18636
|
||||
1-Jun-19,0,140,37,15912,0,2724,0,0,18813
|
||||
25-May-19,0,145,32,15847,1,2803,0,0,18828
|
||||
18-May-19,0,142,31,15985,0,3094,0,0,19252
|
||||
11-May-19,0,142,31,16276,2,3083,0,0,19534
|
||||
4-May-19,0,131,30,16204,8,3001,0,0,19374
|
||||
27-Apr-19,0,126,35,15858,7,2788,0,0,18814
|
||||
20-Apr-19,0,134,30,15809,4,2635,0,0,18612
|
||||
13-Apr-19,0,136,32,15934,8,2545,0,0,18655
|
||||
6-Apr-19,0,136,36,15967,7,2652,0,0,18798
|
||||
30-Mar-19,0,135,37,16346,6,2608,0,0,19132
|
||||
23-Mar-19,0,135,32,16429,6,2537,0,0,19139
|
||||
16-Mar-19,0,131,32,16638,4,2370,0,0,19175
|
||||
9-Mar-19,0,138,32,16351,5,2300,0,0,18826
|
||||
2-Mar-19,0,141,34,16383,6,2436,0,0,19000
|
||||
23-Feb-19,0,145,31,16483,7,2458,0,0,19124
|
||||
16-Feb-19,0,143,32,16565,6,2689,0,0,19435
|
||||
9-Feb-19,0,143,37,16169,5,2551,0,0,18905
|
||||
2-Feb-19,0,142,32,16180,4,2437,0,0,18795
|
||||
26-Jan-19,0,136,34,16314,5,2474,0,0,18963
|
||||
19-Jan-19,0,138,30,16504,6,2276,0,0,18954
|
||||
12-Jan-19,0,140,28,16585,7,2294,0,0,19054
|
||||
5-Jan-19,0,140,28,16175,7,2122,0,0,18472
|
||||
29-Dec-18,0,145,31,15571,7,1974,0,0,17728
|
||||
22-Dec-18,0,144,30,15031,4,2009,0,0,17218
|
||||
15-Dec-18,0,145,30,15884,5,2077,0,0,18141
|
||||
8-Dec-18,0,140,35,16095,5,2193,0,0,18468
|
||||
1-Dec-18,0,148,31,16110,3,2268,0,0,18560
|
||||
24-Nov-18,0,145,27,15817,3,2096,0,0,18088
|
||||
17-Nov-18,0,153,25,15964,6,2104,0,0,18252
|
||||
10-Nov-18,0,153,33,15995,7,2074,0,0,18262
|
||||
3-Nov-18,0,158,34,15944,5,2319,0,0,18460
|
||||
27-Oct-18,0,153,34,15920,4,2400,0,0,18511
|
||||
20-Oct-18,0,150,31,16208,5,2237,0,0,18631
|
||||
13-Oct-18,0,153,30,16470,4,2200,0,0,18857
|
||||
6-Oct-18,0,153,32,15848,4,1926,0,0,17963
|
||||
29-Sep-18,0,155,36,15769,5,1929,0,0,17894
|
||||
22-Sep-18,0,157,29,15572,4,1852,0,0,17614
|
||||
15-Sep-18,0,158,29,15334,5,1998,0,0,17524
|
||||
8-Sep-18,0,161,28,15815,4,1974,0,0,17982
|
||||
1-Sep-18,0,162,30,15825,4,2001,0,0,18022
|
||||
25-Aug-18,0,156,29,15769,4,1929,0,0,17887
|
||||
18-Aug-18,0,152,29,15565,4,1775,0,0,17525
|
||||
11-Aug-18,0,150,30,15460,4,1860,0,0,17504
|
||||
4-Aug-18,0,157,36,15504,5,1979,0,0,17681
|
||||
28-Jul-18,0,157,32,15243,2,1898,0,0,17332
|
||||
21-Jul-18,0,161,31,15322,0,1850,0,0,17364
|
||||
14-Jul-18,0,138,34,14741,0,1725,0,0,16638
|
||||
7-Jul-18,0,158,35,15621,0,1700,0,0,17514
|
||||
30-Jun-18,0,145,31,13791,0,1442,0,0,15409
|
||||
23-Jun-18,0,165,36,15441,0,1594,0,0,17236
|
||||
16-Jun-18,0,162,35,15550,0,1633,0,0,17380
|
||||
9-Jun-18,0,157,39,15502,0,1650,0,0,17348
|
||||
2-Jun-18,0,167,34,15757,3,1795,0,0,17756
|
||||
26-May-18,0,176,33,15551,0,1487,0,0,17247
|
||||
19-May-18,0,173,37,15841,0,1554,0,0,17605
|
||||
12-May-18,0,168,30,16140,0,1400,0,0,17738
|
||||
5-May-18,0,174,34,16049,0,1460,0,0,17717
|
||||
28-Apr-18,0,216,35,16600,0,1485,0,0,18336
|
||||
21-Apr-18,0,181,33,18067,0,1517,0,0,19798
|
||||
14-Apr-18,0,181,32,16873,2,1712,0,0,18800
|
||||
7-Apr-18,0,177,38,16800,4,1558,0,0,18577
|
||||
31-Mar-18,0,184,32,16736,2,1457,0,0,18411
|
||||
24-Mar-18,0,195,31,17965,3,1458,0,0,19652
|
||||
17-Mar-18,0,184,35,16418,2,1380,0,0,18019
|
||||
10-Mar-18,0,184,32,16795,2,1419,0,0,18432
|
||||
3-Mar-18,0,193,35,16780,2,1520,0,0,18530
|
||||
24-Feb-18,0,190,31,16427,2,1338,0,0,17988
|
||||
17-Feb-18,0,204,34,16367,2,1274,0,0,17881
|
||||
10-Feb-18,0,196,36,16275,1,1292,0,0,17800
|
||||
3-Feb-18,0,206,34,16345,3,1527,0,0,18115
|
||||
27-Jan-18,0,196,33,16268,5,1385,0,0,17887
|
||||
20-Jan-18,0,202,36,15935,2,1450,0,0,17625
|
||||
13-Jan-18,0,200,33,15856,7,1320,0,0,17416
|
||||
6-Jan-18,0,209,38,16034,3,1285,0,0,17569
|
||||
30-Dec-17,0,204,39,15544,2,1056,0,0,16845
|
||||
23-Dec-17,0,206,35,15002,2,1039,0,0,16284
|
||||
16-Dec-17,0,203,35,15809,3,1123,0,0,17173
|
||||
9-Dec-17,0,206,32,15921,2,1477,0,0,17638
|
||||
2-Dec-17,0,204,38,16419,2,1339,0,0,18002
|
||||
25-Nov-17,0,210,33,16087,2,1193,0,0,17525
|
||||
18-Nov-17,0,205,39,16106,3,1096,0,0,17449
|
||||
11-Nov-17,0,210,39,16304,2,1081,0,0,17636
|
||||
4-Nov-17,0,227,40,16241,3,1163,0,0,17674
|
||||
28-Oct-17,0,217,39,15860,4,909,0,0,17029
|
||||
21-Oct-17,0,226,39,15948,4,863,0,0,17080
|
||||
14-Oct-17,0,227,43,16086,3,922,0,0,17281
|
||||
7-Oct-17,0,235,45,16215,2,932,0,0,17429
|
||||
30-Sep-17,0,237,38,16037,2,800,0,0,17114
|
||||
23-Sep-17,0,243,42,15838,4,715,0,0,16842
|
||||
16-Sep-17,0,239,39,15716,4,962,0,0,16960
|
||||
9-Sep-17,0,242,44,15784,2,659,0,0,16731
|
||||
2-Sep-17,0,254,46,15812,2,760,0,0,16874
|
||||
26-Aug-17,0,254,43,15805,2,681,0,0,16785
|
||||
19-Aug-17,0,265,45,15748,3,533,0,0,16594
|
||||
12-Aug-17,0,242,38,14836,2,474,0,0,15592
|
||||
5-Aug-17,0,264,39,15242,3,572,0,0,16120
|
||||
29-Jul-17,0,263,41,15266,1,288,0,0,15859
|
||||
22-Jul-17,0,259,44,15357,2,134,0,0,15796
|
||||
15-Jul-17,0,269,38,15512,2,132,0,0,15953
|
||||
8-Jul-17,0,261,47,15717,3,124,0,0,16152
|
||||
1-Jul-17,0,262,43,15617,3,114,0,0,16039
|
||||
24-Jun-17,0,261,44,15799,3,113,0,0,16220
|
||||
17-Jun-17,0,270,42,15745,3,107,0,0,16167
|
||||
10-Jun-17,0,279,42,15587,4,112,0,0,16024
|
||||
3-Jun-17,0,278,49,15721,4,105,0,0,16157
|
||||
27-May-17,0,289,50,15553,4,100,0,0,15996
|
||||
20-May-17,0,286,45,15504,2,92,0,0,15929
|
||||
13-May-17,0,290,46,16796,2,93,0,0,17227
|
||||
6-May-17,0,286,45,17445,4,74,0,0,17854
|
||||
29-Apr-17,0,297,46,17294,3,62,0,0,17702
|
||||
22-Apr-17,0,296,46,17500,3,63,0,0,17908
|
||||
15-Apr-17,0,290,48,17485,3,47,0,0,17873
|
||||
8-Apr-17,0,293,46,17367,3,36,0,0,17745
|
||||
1-Apr-17,0,298,43,17508,7,31,0,0,17887
|
||||
25-Mar-17,0,300,47,17271,10,27,0,0,17655
|
||||
18-Mar-17,0,304,47,17267,14,10,0,0,17642
|
||||
11-Mar-17,0,296,43,16712,19,0,0,0,17070
|
||||
4-Mar-17,0,303,46,16999,3,0,0,0,17351
|
||||
25-Feb-17,0,307,48,16959,0,0,0,0,17314
|
||||
18-Feb-17,0,320,45,16806,0,0,0,0,17171
|
||||
11-Feb-17,0,305,40,16866,0,0,0,0,17211
|
||||
4-Feb-17,0,294,42,16988,0,0,0,0,17324
|
||||
28-Jan-17,0,312,43,16604,0,0,0,0,16959
|
||||
21-Jan-17,0,291,48,16706,0,0,0,0,17045
|
||||
14-Jan-17,0,310,46,16459,0,0,0,0,16815
|
||||
7-Jan-17,0,318,51,16878,0,0,0,0,17247
|
||||
31-Dec-16,0,318,45,15947,0,0,0,0,16310
|
||||
24-Dec-16,0,311,45,15031,0,0,0,0,15387
|
||||
17-Dec-16,0,319,46,16005,0,0,0,0,16370
|
||||
10-Dec-16,0,318,42,16210,0,0,0,0,16570
|
||||
3-Dec-16,0,332,52,16549,0,0,0,0,16933
|
||||
26-Nov-16,0,334,46,16549,0,0,0,0,16929
|
||||
19-Nov-16,0,332,51,15957,0,0,0,0,16340
|
||||
12-Nov-16,0,342,49,16191,0,0,0,0,16582
|
||||
5-Nov-16,0,345,56,15339,0,0,0,0,15740
|
||||
29-Oct-16,0,349,49,14911,0,0,0,0,15309
|
||||
22-Oct-16,0,350,53,15040,0,0,0,0,15443
|
||||
15-Oct-16,0,348,53,15238,0,0,0,0,15639
|
||||
8-Oct-16,0,350,52,14567,0,0,0,0,14969
|
||||
1-Oct-16,0,352,63,15041,0,0,0,0,15456
|
||||
24-Sep-16,0,351,60,14551,0,0,0,0,14962
|
||||
17-Sep-16,0,347,58,14655,0,0,0,0,15060
|
||||
10-Sep-16,0,338,62,14584,0,0,0,0,14984
|
||||
3-Sep-16,0,355,62,14582,0,0,0,0,14999
|
||||
27-Aug-16,0,352,67,14673,0,0,0,0,15092
|
||||
20-Aug-16,0,359,63,14400,0,0,0,0,14822
|
||||
13-Aug-16,0,392,61,14415,0,0,0,0,14868
|
||||
6-Aug-16,0,373,62,14653,0,0,0,0,15088
|
||||
30-Jul-16,0,367,60,14607,0,0,0,0,15034
|
||||
23-Jul-16,0,374,58,14741,0,0,0,0,15173
|
||||
16-Jul-16,0,415,57,14959,0,0,0,0,15431
|
||||
9-Jul-16,0,439,58,13846,0,0,0,0,14343
|
||||
2-Jul-16,0,378,59,14610,0,0,0,0,15047
|
||||
25-Jun-16,0,392,62,14607,0,0,0,0,15061
|
||||
18-Jun-16,0,380,65,14953,0,0,0,0,15398
|
||||
11-Jun-16,0,398,58,14869,0,0,0,0,15325
|
||||
4-Jun-16,0,379,63,14444,0,0,0,0,14886
|
||||
28-May-16,0,406,68,14443,0,0,0,0,14917
|
||||
21-May-16,0,396,68,14739,0,0,0,0,15203
|
||||
14-May-16,0,391,58,14553,0,0,0,0,15002
|
||||
7-May-16,0,393,72,14577,0,0,0,0,15042
|
||||
30-Apr-16,0,391,67,14347,0,0,0,0,14805
|
||||
23-Apr-16,0,412,65,14447,0,0,0,0,14924
|
||||
16-Apr-16,0,392,65,14611,0,0,0,0,15068
|
||||
9-Apr-16,0,414,68,14426,0,0,0,0,14908
|
||||
2-Apr-16,0,410,67,14537,0,0,0,0,15014
|
||||
26-Mar-16,0,407,74,14040,0,0,0,0,14521
|
||||
19-Mar-16,0,418,73,13893,0,0,0,0,14384
|
||||
12-Mar-16,0,419,76,14256,0,0,0,0,14751
|
||||
5-Mar-16,0,440,73,14272,0,0,0,0,14785
|
||||
27-Feb-16,0,437,78,14293,0,0,0,0,14808
|
||||
20-Feb-16,0,448,78,14500,0,0,0,0,15026
|
||||
13-Feb-16,0,458,74,13949,0,0,0,0,14481
|
||||
6-Feb-16,0,437,76,13681,0,0,0,0,14194
|
||||
30-Jan-16,0,428,76,13729,0,0,0,0,14233
|
||||
23-Jan-16,0,435,80,13393,0,0,0,0,13908
|
||||
16-Jan-16,0,441,79,13369,0,0,0,0,13889
|
||||
9-Jan-16,0,443,77,13298,0,0,0,0,13818
|
||||
2-Jan-16,0,450,77,12385,0,0,0,0,12912
|
||||
26-Dec-15,0,446,84,11190,0,0,0,0,11720
|
||||
19-Dec-15,0,452,92,11888,0,0,0,0,12432
|
||||
12-Dec-15,0,468,84,12623,0,0,0,0,13175
|
||||
5-Dec-15,0,465,87,12736,0,0,0,0,13288
|
||||
28-Nov-15,0,460,86,12721,0,0,0,0,13267
|
||||
21-Nov-15,0,465,88,12423,0,0,0,0,12976
|
||||
14-Nov-15,0,494,90,12483,0,0,0,0,13067
|
||||
7-Nov-15,0,489,89,12383,0,0,0,0,12961
|
||||
31-Oct-15,0,509,87,12512,0,0,0,0,13108
|
||||
24-Oct-15,0,495,88,12346,0,0,0,0,12929
|
||||
17-Oct-15,0,505,90,12338,0,0,0,0,12933
|
||||
10-Oct-15,0,487,96,11995,0,0,0,0,12578
|
||||
3-Oct-15,0,489,87,11676,0,0,0,0,12252
|
||||
26-Sep-15,0,480,93,11015,0,0,0,0,11588
|
||||
19-Sep-15,0,474,85,10385,0,0,0,0,10944
|
||||
12-Sep-15,0,506,94,11041,0,0,0,0,11641
|
||||
5-Sep-15,0,519,101,11402,0,0,0,0,12022
|
||||
29-Aug-15,0,528,106,11556,0,0,0,0,12190
|
||||
22-Aug-15,0,596,106,11672,0,0,0,0,12374
|
||||
15-Aug-15,0,530,105,11574,0,0,0,0,12209
|
||||
8-Aug-15,1,520,103,11089,0,0,0,0,11713
|
||||
1-Aug-15,1,507,106,10947,0,0,0,0,11561
|
||||
25-Jul-15,1,593,107,10712,0,0,0,0,11413
|
||||
18-Jul-15,1,520,104,10533,0,0,0,0,11158
|
||||
11-Jul-15,1,538,110,10764,0,0,0,0,11413
|
||||
4-Jul-15,1,550,108,10918,0,0,0,0,11577
|
||||
27-Jun-15,1,537,114,10783,0,0,0,0,11435
|
||||
20-Jun-15,1,564,109,10961,0,0,0,0,11635
|
||||
13-Jun-15,1,550,114,10845,0,0,0,0,11510
|
||||
6-Jun-15,1,540,112,10369,0,0,0,0,11022
|
||||
30-May-15,1,537,115,10137,0,0,0,0,10790
|
||||
23-May-15,1,532,116,10024,0,0,0,0,10673
|
||||
16-May-15,1,556,115,10051,0,0,0,0,10723
|
||||
9-May-15,1,549,112,9923,0,0,0,0,10585
|
||||
2-May-15,1,549,115,9700,0,0,0,0,10365
|
||||
25-Apr-15,1,555,118,9462,0,0,0,0,10136
|
||||
18-Apr-15,1,552,121,9363,0,0,0,0,10037
|
||||
11-Apr-15,1,549,122,9046,0,0,0,0,9718
|
||||
4-Apr-15,1,561,130,8955,0,0,0,0,9647
|
||||
28-Mar-15,1,570,125,9064,0,0,0,0,9760
|
||||
21-Mar-15,1,568,134,9194,0,0,0,0,9897
|
||||
14-Mar-15,1,561,137,9163,0,0,0,0,9862
|
||||
7-Mar-15,1,553,127,8826,0,0,0,0,9507
|
||||
28-Feb-15,1,587,130,8638,0,0,0,0,9356
|
||||
21-Feb-15,1,584,124,8459,0,0,0,0,9168
|
||||
14-Feb-15,1,588,136,8314,0,0,0,0,9039
|
||||
7-Feb-15,1,577,142,8240,0,0,0,0,8960
|
||||
31-Jan-15,1,583,133,8003,0,0,0,0,8720
|
||||
24-Jan-15,1,598,130,7738,0,0,0,0,8467
|
||||
17-Jan-15,1,593,138,7779,0,0,0,0,8511
|
||||
10-Jan-15,1,579,137,7654,0,0,0,0,8371
|
||||
3-Jan-15,1,581,127,7500,0,0,0,0,8209
|
||||
27-Dec-14,1,562,131,6455,0,0,0,0,7149
|
||||
20-Dec-14,1,582,129,6575,0,0,0,0,7287
|
||||
13-Dec-14,1,602,134,7342,0,0,0,0,8079
|
||||
6-Dec-14,1,602,139,7319,0,0,0,0,8061
|
||||
29-Nov-14,1,609,134,7171,0,0,0,0,7915
|
||||
22-Nov-14,1,630,144,7088,0,0,0,0,7863
|
||||
15-Nov-14,1,625,160,7301,0,0,0,0,8087
|
||||
8-Nov-14,1,617,151,7029,0,0,0,0,7798
|
||||
1-Nov-14,2,635,166,6769,0,0,0,0,7572
|
||||
25-Oct-14,1,614,160,6508,0,0,0,0,7283
|
||||
18-Oct-14,1,609,159,6348,0,0,0,0,7117
|
||||
11-Oct-14,1,627,155,6349,0,0,0,0,7132
|
||||
4-Oct-14,1,621,165,5897,0,0,0,0,6684
|
||||
27-Sep-14,1,622,158,5854,0,0,0,0,6635
|
||||
20-Sep-14,1,633,164,5881,0,0,0,0,6679
|
||||
13-Sep-14,1,654,161,5789,0,0,0,0,6605
|
||||
6-Sep-14,1,652,166,5700,0,0,0,0,6519
|
||||
30-Aug-14,2,643,171,5580,0,0,0,0,6396
|
||||
23-Aug-14,2,643,171,5466,0,0,0,0,6282
|
||||
16-Aug-14,2,633,184,5304,0,0,0,0,6123
|
||||
9-Aug-14,2,648,185,5183,0,0,0,0,6018
|
||||
2-Aug-14,2,641,183,5247,0,0,0,0,6073
|
||||
26-Jul-14,2,643,185,4938,0,0,0,0,5768
|
||||
19-Jul-14,2,640,183,4814,0,0,0,0,5639
|
||||
12-Jul-14,2,719,175,4713,0,0,0,0,5609
|
||||
5-Jul-14,2,652,177,4543,0,0,0,0,5374
|
||||
28-Jun-14,2,653,182,4526,0,0,0,0,5363
|
||||
21-Jun-14,2,655,181,4490,0,0,0,0,5328
|
||||
14-Jun-14,2,651,166,3895,0,0,0,0,4714
|
||||
7-Jun-14,2,674,187,3866,0,0,0,0,4729
|
||||
31-May-14,2,665,183,3623,0,0,0,0,4473
|
||||
24-May-14,2,669,194,3581,0,0,0,0,4446
|
||||
17-May-14,2,669,191,3346,0,0,0,0,4208
|
||||
10-May-14,2,671,196,3221,0,0,0,0,4090
|
||||
3-May-14,2,661,192,3223,0,0,0,0,4078
|
||||
26-Apr-14,2,681,194,3095,0,0,0,0,3972
|
||||
19-Apr-14,2,687,201,2981,0,0,0,0,3871
|
||||
12-Apr-14,2,695,203,2910,0,0,0,0,3810
|
||||
5-Apr-14,2,694,218,2807,0,0,0,0,3721
|
||||
29-Mar-14,2,725,222,2807,0,0,0,0,3756
|
||||
22-Mar-14,2,709,226,2661,0,0,0,0,3598
|
||||
15-Mar-14,2,712,235,2544,0,0,0,0,3493
|
||||
8-Mar-14,2,747,241,2405,0,0,0,0,3395
|
||||
1-Mar-14,2,715,255,2368,0,0,0,0,3340
|
||||
22-Feb-14,2,748,244,2245,0,0,0,0,3239
|
||||
15-Feb-14,2,707,248,2107,0,0,0,0,3064
|
||||
8-Feb-14,2,759,256,2044,0,0,0,0,3061
|
||||
1-Feb-14,2,734,258,1928,0,0,0,0,2922
|
||||
25-Jan-14,2,753,263,1781,0,0,0,0,2799
|
||||
18-Jan-14,2,749,260,1722,0,0,0,0,2733
|
||||
11-Jan-14,2,728,288,1612,0,0,0,0,2630
|
||||
4-Jan-14,2,755,276,1343,0,0,0,0,2376
|
||||
28-Dec-13,2,736,285,1082,0,0,0,0,2105
|
||||
21-Dec-13,2,711,276,1005,0,0,0,0,1994
|
||||
14-Dec-13,2,769,285,1118,0,0,0,0,2174
|
||||
7-Dec-13,2,756,281,1008,0,0,0,0,2047
|
||||
30-Nov-13,2,799,286,920,0,0,0,0,2007
|
||||
23-Nov-13,2,898,290,849,0,0,0,0,2039
|
||||
16-Nov-13,2,956,313,790,0,0,0,0,2061
|
||||
9-Nov-13,2,939,313,746,0,0,0,0,2000
|
||||
2-Nov-13,2,963,327,662,0,0,0,0,1954
|
||||
26-Oct-13,2,992,322,636,0,0,0,0,1952
|
||||
19-Oct-13,2,977,309,651,0,0,0,0,1939
|
||||
12-Oct-13,2,990,328,576,0,0,0,0,1896
|
||||
5-Oct-13,2,887,328,560,0,0,0,0,1777
|
||||
28-Sep-13,2,1450,331,516,0,0,0,0,2299
|
||||
21-Sep-13,2,1510,336,449,0,0,0,0,2297
|
||||
14-Sep-13,2,1530,335,423,0,0,0,0,2290
|
||||
7-Sep-13,2,874,340,363,0,0,0,0,1579
|
||||
31-Aug-13,2,887,353,317,0,0,0,0,1559
|
||||
24-Aug-13,2,870,349,269,0,0,0,0,1490
|
||||
17-Aug-13,2,877,352,235,0,0,0,0,1466
|
||||
10-Aug-13,2,862,367,190,0,0,0,0,1421
|
||||
3-Aug-13,2,841,399,149,0,0,0,0,1391
|
||||
27-Jul-13,2,876,416,104,0,0,0,0,1398
|
||||
20-Jul-13,2,887,457,15,0,0,0,0,1361
|
||||
13-Jul-13,2,879,445,0,0,0,0,0,1326
|
||||
6-Jul-13,2,876,462,0,0,0,0,0,1340
|
||||
29-Jun-13,2,884,464,0,0,0,0,0,1350
|
||||
22-Jun-13,2,901,452,0,0,0,0,0,1355
|
||||
15-Jun-13,2,893,460,0,0,0,0,0,1355
|
||||
8-Jun-13,2,956,474,0,0,0,0,0,1432
|
||||
1-Jun-13,2,1117,473,0,0,0,0,0,1592
|
||||
25-May-13,2,1063,441,0,0,0,0,0,1506
|
||||
18-May-13,2,944,383,0,0,0,0,0,1329
|
||||
11-May-13,2,1109,450,0,0,0,0,0,1561
|
||||
4-May-13,2,1020,451,0,0,0,0,0,1473
|
||||
27-Apr-13,2,1003,450,0,0,0,0,0,1455
|
||||
20-Apr-13,2,967,448,0,0,0,0,0,1417
|
||||
13-Apr-13,2,1061,469,0,0,0,0,0,1532
|
||||
6-Apr-13,2,1022,478,0,0,0,0,0,1502
|
||||
30-Mar-13,2,1030,440,0,0,0,0,0,1472
|
||||
23-Mar-13,2,1101,464,0,0,0,0,0,1567
|
||||
16-Mar-13,2,1047,451,0,0,0,0,0,1500
|
||||
9-Mar-13,2,1046,476,0,0,0,0,0,1524
|
||||
2-Mar-13,3,1044,469,0,0,0,0,0,1516
|
||||
23-Feb-13,3,1077,479,0,0,0,0,0,1559
|
||||
16-Feb-13,3,1094,458,0,0,0,0,0,1555
|
||||
9-Feb-13,3,1091,468,0,0,0,0,0,1562
|
||||
2-Feb-13,3,1099,447,0,0,0,0,0,1549
|
||||
26-Jan-13,3,1132,452,0,0,0,0,0,1587
|
||||
19-Jan-13,3,1124,455,0,0,0,0,0,1582
|
||||
12-Jan-13,4,1164,446,0,0,0,0,0,1614
|
||||
5-Jan-13,3,1167,443,0,0,0,0,0,1613
|
||||
29-Dec-12,3,1085,411,0,0,0,0,0,1499
|
||||
22-Dec-12,3,1076,407,0,0,0,0,0,1486
|
||||
15-Dec-12,4,1173,437,0,0,0,0,0,1614
|
||||
8-Dec-12,4,1208,441,0,0,0,0,0,1653
|
||||
1-Dec-12,3,1171,476,0,0,0,0,0,1650
|
||||
24-Nov-12,3,1185,474,0,0,0,0,0,1662
|
||||
17-Nov-12,3,1163,460,0,0,0,0,0,1626
|
||||
10-Nov-12,3,1200,451,0,0,0,0,0,1654
|
||||
3-Nov-12,3,1247,476,0,0,0,0,0,1726
|
||||
27-Oct-12,3,1219,456,0,0,0,0,0,1678
|
||||
20-Oct-12,3,1276,443,0,0,0,0,0,1722
|
||||
|
@@ -0,0 +1,24 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
Drupal.charts_api_example = Drupal.charts_api_example || {};
|
||||
// PLEASE NOTE: this example is structured to work for Highcharts, but you
|
||||
// do the same thing for any of the charting libraries.
|
||||
Drupal.charts_api_example.highchartsTooltipFormatter = function () {
|
||||
return 'The value for <b>' + this.x +
|
||||
'</b> is <b>' + this.y + '</b>';
|
||||
};
|
||||
Drupal.behaviors.chartsApiExampleCharts = {
|
||||
attach: function (context, settings) {
|
||||
let chartcontainer = document.getElementById('exampleidjs');
|
||||
chartcontainer.addEventListener('drupalChartsConfigsInitialization', function (e) {
|
||||
let data = e.detail;
|
||||
const id = data.drupalChartDivId;
|
||||
// Change the background of the chart to green.
|
||||
data.chart.backgroundColor = 'green';
|
||||
// Replace the tooltip formatter.
|
||||
data.tooltip.formatter = Drupal.charts_api_example.highchartsTooltipFormatter;
|
||||
Drupal.Charts.Contents.update(id, data);
|
||||
});
|
||||
}
|
||||
};
|
||||
}());
|
||||
@@ -0,0 +1,400 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\charts_api_example\Controller;
|
||||
|
||||
use Drupal\Component\Uuid\UuidInterface;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Extension\ModuleExtensionList;
|
||||
use Drupal\Core\Messenger\MessengerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Charts Api Example.
|
||||
*/
|
||||
class ChartsApiExample extends ControllerBase {
|
||||
|
||||
/**
|
||||
* The messenger service.
|
||||
*
|
||||
* @var \Drupal\Core\Messenger\MessengerInterface
|
||||
*/
|
||||
protected $messenger;
|
||||
|
||||
/**
|
||||
* The UUID service.
|
||||
*
|
||||
* @var \Drupal\Component\Uuid\UuidInterface
|
||||
*/
|
||||
protected $uuidService;
|
||||
|
||||
/**
|
||||
* The module extension list.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleExtensionList
|
||||
*/
|
||||
protected $moduleList;
|
||||
|
||||
/**
|
||||
* Construct.
|
||||
*
|
||||
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
|
||||
* The messenger service.
|
||||
* @param \Drupal\Component\Uuid\UuidInterface $uuidService
|
||||
* The UUID service.
|
||||
* @param \Drupal\Core\Extension\ModuleExtensionList $module_list
|
||||
* The module list.
|
||||
*/
|
||||
public function __construct(MessengerInterface $messenger, UuidInterface $uuidService, ModuleExtensionList $module_list) {
|
||||
$this->messenger = $messenger;
|
||||
$this->uuidService = $uuidService;
|
||||
$this->moduleList = $module_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('messenger'),
|
||||
$container->get('uuid'),
|
||||
$container->get('extension.list.module')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the dashboard of charts.
|
||||
*
|
||||
* @return array
|
||||
* Array to render.
|
||||
*/
|
||||
public function display() {
|
||||
|
||||
/*
|
||||
* If you do not set a library specifically in your render array, Charts
|
||||
* will use your default. But you do need to set your default library.
|
||||
*/
|
||||
$charts_settings = $this->config('charts.settings');
|
||||
$library = $charts_settings->get('charts_default_settings.library');
|
||||
if (empty($library)) {
|
||||
$this->messenger->addError($this->t('You need to first configure Charts default settings'));
|
||||
return [];
|
||||
}
|
||||
|
||||
// This is a container for all the charts below.
|
||||
$charts_container = [
|
||||
'#type' => 'container',
|
||||
'content' => [],
|
||||
];
|
||||
|
||||
/* Listing all the default chart types except 'gauge' and 'scatter'
|
||||
* (they come later).
|
||||
*/
|
||||
$chart_types = ['area', 'bar', 'column', 'line', 'spline', 'pie', 'donut'];
|
||||
|
||||
// Define a series to be used in multiple examples.
|
||||
$series = [
|
||||
'#type' => 'chart_data',
|
||||
'#title' => $this->t('5.0.x'),
|
||||
'#data' => [257, 235, 325, 340],
|
||||
'#color' => '#1d84c3',
|
||||
];
|
||||
|
||||
// Define an x-axis to be used in multiple examples.
|
||||
$xaxis = [
|
||||
'#type' => 'chart_xaxis',
|
||||
'#title' => $this->t('Months'),
|
||||
'#labels' => [
|
||||
$this->t('January 2021'),
|
||||
$this->t('February 2021'),
|
||||
$this->t('March 2021'),
|
||||
$this->t('April 2021'),
|
||||
],
|
||||
];
|
||||
|
||||
// Define a y-axis to be used in multiple examples.
|
||||
$yaxis = [
|
||||
'#type' => 'chart_yaxis',
|
||||
'#title' => $this->t('Number of Installs'),
|
||||
];
|
||||
|
||||
// Iterate through the chart types and build a chart for each.
|
||||
foreach ($chart_types as $type) {
|
||||
$charts_container['content'][$type] = [
|
||||
'#type' => 'chart',
|
||||
'#tooltips' => $charts_settings->get('charts_default_settings.display.tooltips'),
|
||||
'#title' => $this->t('@library @type Chart', [
|
||||
'@library' => ucfirst($library),
|
||||
'@type' => ucfirst($type),
|
||||
]),
|
||||
'#chart_type' => $type,
|
||||
'series' => $series,
|
||||
'x_axis' => $xaxis,
|
||||
'y_axis' => $yaxis,
|
||||
'#raw_options' => [],
|
||||
// e.g. ['chart' => ['backgroundColor' => '#000000']].
|
||||
];
|
||||
}
|
||||
|
||||
// Column chart with two series.
|
||||
$charts_container['content']['two_series_column'] = [
|
||||
'#type' => 'chart',
|
||||
'#tooltips' => $charts_settings->get('charts_default_settings.display.tooltips'),
|
||||
'#title' => $this->t('@library Column Chart (Two Series)', ['@library' => ucfirst($library)]),
|
||||
'#chart_type' => 'column',
|
||||
'series_one' => $series,
|
||||
'series_two' => [
|
||||
'#type' => 'chart_data',
|
||||
'#title' => $this->t('8.x-3.x'),
|
||||
'#data' => [4330, 4413, 4212, 4431],
|
||||
'#color' => '#77b259',
|
||||
],
|
||||
'x_axis' => $xaxis,
|
||||
'y_axis' => $yaxis,
|
||||
'#raw_options' => [],
|
||||
];
|
||||
|
||||
// Stacked Area Chart from a local CSV file.
|
||||
$file_contents = $this->getCsvContents();
|
||||
$charts_container['content']['from_csv_file'] = [
|
||||
'#type' => 'chart',
|
||||
'#tooltips' => $charts_settings->get('charts_default_settings.display.tooltips'),
|
||||
'#title' => $this->t('@library Stacked Area Chart from CSV File', ['@library' => ucfirst($library)]),
|
||||
'#chart_type' => 'area',
|
||||
'series_seven_2' => [
|
||||
'#type' => 'chart_data',
|
||||
'#title' => $this->t('7.x-2.x'),
|
||||
// Using array_reverse because my file is desc rather than asc.
|
||||
'#data' => array_reverse($file_contents['7.x-2.x']),
|
||||
'#color' => '#76b7b2',
|
||||
],
|
||||
'series_eight_three' => [
|
||||
'#type' => 'chart_data',
|
||||
'#title' => $this->t('8.x-3.x'),
|
||||
'#data' => array_reverse($file_contents['8.x-3.x']),
|
||||
'#color' => '#edc949',
|
||||
],
|
||||
'series_five_zero' => [
|
||||
'#type' => 'chart_data',
|
||||
'#title' => $this->t('5.0.x'),
|
||||
'#data' => array_reverse($file_contents['5.0.x']),
|
||||
'#color' => '#ff9da7',
|
||||
],
|
||||
'x_axis' => [
|
||||
'#type' => 'chart_xaxis',
|
||||
'#title' => $this->t('Week'),
|
||||
'#labels' => array_reverse($file_contents['Week']),
|
||||
],
|
||||
'y_axis' => [
|
||||
'#type' => 'chart_yaxis',
|
||||
'#title' => $this->t('Number of Installs'),
|
||||
],
|
||||
'#stacking' => TRUE,
|
||||
];
|
||||
if ($library === 'chartjs') {
|
||||
// A real-life #raw_options use-case.
|
||||
$charts_container['content']['from_csv_file']['#raw_options'] = [
|
||||
'options' => [
|
||||
'scales' => [
|
||||
'x' => [
|
||||
'ticks' => [
|
||||
'autoSkip' => TRUE,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
elseif ($library === 'billboard' || $library === 'c3') {
|
||||
$charts_container['content']['from_csv_file']['#raw_options'] = [
|
||||
'axis' => [
|
||||
'x' => [
|
||||
'tick' => [
|
||||
'culling' => TRUE,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// Stacked column chart with two series.
|
||||
$charts_container['content']['stacked_two_series_column'] = [
|
||||
'#type' => 'chart',
|
||||
'#tooltips' => $charts_settings->get('charts_default_settings.display.tooltips'),
|
||||
'#title' => $this->t('@library Stacked Column Chart (Two Series)', ['@library' => ucfirst($library)]),
|
||||
'#chart_type' => 'column',
|
||||
'series_one' => $series,
|
||||
'series_two' => [
|
||||
'#type' => 'chart_data',
|
||||
'#title' => $this->t('8.x-3.x'),
|
||||
'#data' => [4330, 4413, 4212, 4431],
|
||||
'#color' => '#77b259',
|
||||
],
|
||||
'x_axis' => $xaxis,
|
||||
'y_axis' => $yaxis,
|
||||
'#stacking' => TRUE,
|
||||
'#raw_options' => [],
|
||||
];
|
||||
|
||||
// Combination chart (column and line).
|
||||
$charts_container['content']['combo'] = [
|
||||
'#type' => 'chart',
|
||||
'#tooltips' => $charts_settings->get('charts_default_settings.display.tooltips'),
|
||||
'#title' => $this->t('@library Combination Chart', ['@library' => ucfirst($library)]),
|
||||
'#chart_type' => 'column',
|
||||
'series_one' => $series,
|
||||
'series_two' => [
|
||||
'#type' => 'chart_data',
|
||||
'#chart_type' => 'line',
|
||||
'#title' => $this->t('8.x-3.x'),
|
||||
'#data' => [4330, 4413, 4212, 4431],
|
||||
'#color' => '#77b259',
|
||||
],
|
||||
'x_axis' => $xaxis,
|
||||
'y_axis' => $yaxis,
|
||||
'#raw_options' => [],
|
||||
];
|
||||
|
||||
// Combination chart (column and line) with secondary Y-Axis.
|
||||
$charts_container['content']['combo_dual_yaxes'] = [
|
||||
'#type' => 'chart',
|
||||
'#tooltips' => $charts_settings->get('charts_default_settings.display.tooltips'),
|
||||
'#title' => $this->t('@library Combination Chart with Secondary Y-Axis', ['@library' => ucfirst($library)]),
|
||||
'#chart_type' => 'column',
|
||||
'series_one' => $series,
|
||||
'series_two' => [
|
||||
'#type' => 'chart_data',
|
||||
'#chart_type' => 'line',
|
||||
'#title' => $this->t('8.x-3.x'),
|
||||
'#data' => [4330, 4413, 4212, 4431],
|
||||
'#color' => '#77b259',
|
||||
'#target_axis' => 'y_axis_secondary',
|
||||
],
|
||||
'x_axis' => $xaxis,
|
||||
'y_axis' => $yaxis,
|
||||
'y_axis_secondary' => [
|
||||
'#type' => 'chart_yaxis',
|
||||
'#title' => $this->t('Secondary Y-Axis'),
|
||||
'#opposite' => TRUE,
|
||||
],
|
||||
'#raw_options' => [],
|
||||
];
|
||||
|
||||
// Radar chart. Not supported by C3.js or Google Charts (natively).
|
||||
if (!in_array($library, ['c3', 'google'])) {
|
||||
$charts_container['content']['radar'] = [
|
||||
'#type' => 'chart',
|
||||
'#tooltips' => $charts_settings->get('charts_default_settings.display.tooltips'),
|
||||
'#title' => $this->t('@library Radar Chart', ['@library' => ucfirst($library)]),
|
||||
'#chart_type' => 'line',
|
||||
'series' => $series,
|
||||
'x_axis' => $xaxis,
|
||||
'y_axis' => $yaxis,
|
||||
'#polar' => TRUE,
|
||||
'#raw_options' => [],
|
||||
];
|
||||
}
|
||||
|
||||
// Gauge chart. Not yet supported by Chart.js (as of 3.5.1).
|
||||
if ($library !== 'chartjs') {
|
||||
$charts_container['content']['gauge'] = [
|
||||
'#type' => 'chart',
|
||||
'#title' => $this->t('@library Gauge Chart', ['@library' => ucfirst($library)]),
|
||||
'#chart_type' => 'gauge',
|
||||
'#gauge' => [
|
||||
'green_to' => 100,
|
||||
'green_from' => 75,
|
||||
'yellow_to' => 74,
|
||||
'yellow_from' => 50,
|
||||
'red_to' => 49,
|
||||
'red_from' => 0,
|
||||
'max' => 100,
|
||||
'min' => 0,
|
||||
],
|
||||
'series' => [
|
||||
'#type' => 'chart_data',
|
||||
'#title' => $this->t('Speed'),
|
||||
'#data' => [65],
|
||||
],
|
||||
'#raw_options' => [],
|
||||
];
|
||||
}
|
||||
|
||||
// Scatter chart.
|
||||
$charts_container['content']['scatter'] = [
|
||||
'#type' => 'chart',
|
||||
'#tooltips' => $charts_settings->get('charts_default_settings.display.tooltips'),
|
||||
'#title' => $this->t('@library Scatter Chart', ['@library' => ucfirst($library)]),
|
||||
'#data_markers' => TRUE,
|
||||
'#chart_type' => 'scatter',
|
||||
'series' => [
|
||||
'#type' => 'chart_data',
|
||||
'#title' => $this->t('Group 1'),
|
||||
'#data' => [[162.2, 51.8], [164.5, 58.0], [160.5, 49.6], [154.0, 65.0]],
|
||||
],
|
||||
'x_axis' => [
|
||||
'#type' => 'chart_xaxis',
|
||||
'#title' => $this->t('Height'),
|
||||
'#labels' => [],
|
||||
],
|
||||
'y_axis' => [
|
||||
'#type' => 'chart_yaxis',
|
||||
'#title' => $this->t('Weight'),
|
||||
],
|
||||
'#stacking' => TRUE,
|
||||
'#raw_options' => [],
|
||||
];
|
||||
|
||||
if ($library === 'highcharts') {
|
||||
$charts_container['content']['php_override'] = [
|
||||
'#chart_id' => 'exampleidphp',
|
||||
'#type' => 'chart',
|
||||
'#tooltips' => $charts_settings->get('charts_default_settings.display.tooltips'),
|
||||
'#title' => $this->t('@library Chart, Overridden By PHP Hook', ['@library' => ucfirst($library)]),
|
||||
'#chart_type' => 'column',
|
||||
'series' => $series,
|
||||
'x_axis' => $xaxis,
|
||||
'y_axis' => $yaxis,
|
||||
'#color_changer' => TRUE,
|
||||
'#raw_options' => [],
|
||||
];
|
||||
|
||||
$charts_container['content']['js_override'] = [
|
||||
'#id' => 'exampleidjs',
|
||||
'#chart_id' => 'exampleidjschart',
|
||||
'#type' => 'chart',
|
||||
'#tooltips' => $charts_settings->get('charts_default_settings.display.tooltips'),
|
||||
'#title' => $this->t('@library Chart, Overridden By JS Function', ['@library' => ucfirst($library)]),
|
||||
'#chart_type' => 'column',
|
||||
'series' => $series,
|
||||
'x_axis' => $xaxis,
|
||||
'y_axis' => $yaxis,
|
||||
'#raw_options' => [],
|
||||
];
|
||||
}
|
||||
|
||||
return $charts_container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the CSV contents in an array with data organized by column.
|
||||
*
|
||||
* @return array
|
||||
* The array of rows.
|
||||
*/
|
||||
private function getCsvContents() {
|
||||
$file_path = $this->moduleList->getPath('charts_api_example');
|
||||
$file_name = $file_path . '/fixtures/charts_api_example_file.csv';
|
||||
$handle = fopen($file_name, 'r');
|
||||
$all_rows = [];
|
||||
while ($row = fgetcsv($handle)) {
|
||||
$all_rows['Week'][] = $row[0];
|
||||
$all_rows['7.x-2.x'][] = (int) $row[4];
|
||||
$all_rows['8.x-3.x'][] = (int) $row[6];
|
||||
$all_rows['5.0.x'][] = (int) $row[8];
|
||||
}
|
||||
fclose($handle);
|
||||
|
||||
return $all_rows;
|
||||
}
|
||||
|
||||
}
|
||||
117
web/modules/charts/modules/charts_billboard/README.md
Executable file
117
web/modules/charts/modules/charts_billboard/README.md
Executable file
@@ -0,0 +1,117 @@
|
||||
#Installation Using Composer (recommended)
|
||||
|
||||
If you use Composer to manage dependencies, edit your site's "composer.json"
|
||||
file as follows.
|
||||
|
||||
1. Run the following command to ensure that you have the "composer/installers"
|
||||
package installed. This package facilitates the installation of packages into
|
||||
directories other than "/vendor" (e.g. "/libraries") using Composer.
|
||||
|
||||
composer require --prefer-dist composer/installers
|
||||
|
||||
2. Add the following to the "installer-paths" section of "composer.json":
|
||||
|
||||
"libraries/{$name}": ["type:drupal-library"],
|
||||
|
||||
3. Add the following to the "repositories" section of "composer.json":
|
||||
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "billboardjs/billboard",
|
||||
"version": "3.10.3",
|
||||
"type": "drupal-library",
|
||||
"extra": {
|
||||
"installer-name": "billboard"
|
||||
},
|
||||
"dist": {
|
||||
"url": "https://registry.npmjs.org/billboard.js/-/billboard.js-3.10.3.tgz",
|
||||
"type": "tar"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "d3/d3",
|
||||
"version": "7.8.5",
|
||||
"type": "drupal-library",
|
||||
"extra": {
|
||||
"installer-name": "d3"
|
||||
},
|
||||
"dist": {
|
||||
"url": "https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.js",
|
||||
"type": "file"
|
||||
},
|
||||
"require": {
|
||||
"composer/installers": "^1.0 || ^2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4. This and the following step is optional but recommended. The reason for
|
||||
them is that when installing the Billboard.js package with Composer,
|
||||
additional files are added into the library directory. These files are not
|
||||
necessary and can be potentially harmful to your site, so it's best to remove
|
||||
them. So: create a new directory in your project root called "scripts".
|
||||
5. Inside that directory, create a new file called "clean-billboardjs.sh" and
|
||||
paste the following into it:
|
||||
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
declare -a directories=(
|
||||
"web/libraries/billboard/dist-esm"
|
||||
"web/libraries/billboard/src"
|
||||
"web/libraries/billboard/types"
|
||||
"web/libraries/billboard/dist/plugin"
|
||||
"web/libraries/billboard/dist/theme"
|
||||
)
|
||||
counter=0
|
||||
echo "Deleting unneeded directories inside web/libraries/billboard"
|
||||
for directory in "${directories[@]}"
|
||||
do
|
||||
if [ -d $directory ]; then
|
||||
echo "Deleting $directory"
|
||||
rm -rf $directory
|
||||
counter=$((counter+1))
|
||||
fi
|
||||
done
|
||||
echo "$counter folders were deleted"
|
||||
declare -a files=(
|
||||
"web/libraries/billboard/CONTRIBUTING.md"
|
||||
"web/libraries/billboard/README.md"
|
||||
"web/libraries/billboard/LICENSE"
|
||||
"web/libraries/billboard/package.json"
|
||||
"web/libraries/billboard/dist/package.json"
|
||||
)
|
||||
counter=0
|
||||
echo "Deleting unneeded files inside web/libraries/billboard"
|
||||
for file in "${files[@]}"
|
||||
do
|
||||
if [[ -f $file ]]; then
|
||||
echo "Deleting $file"
|
||||
rm $file
|
||||
counter=$((counter+1))
|
||||
fi
|
||||
done
|
||||
echo "$counter files were deleted"
|
||||
|
||||
6. Add a "scripts" entry to your "composer.json" file as shown below. If
|
||||
"scripts" already exists, you will need to do a little more to incorporate
|
||||
the code below.
|
||||
|
||||
"scripts": {
|
||||
"clean-billboardjs": "chmod +x scripts/clean-billboardjs.sh &&
|
||||
./scripts/clean-billboardjs.sh",
|
||||
"post-install-cmd": [
|
||||
"@clean-billboardjs"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"@clean-billboardjs"
|
||||
]
|
||||
}
|
||||
|
||||
7. Run the following command; you should find that new directories have been
|
||||
created under "/libraries".
|
||||
|
||||
composer require --prefer-dist billboardjs/billboard:3.10.3 d3/d3:7.8.5
|
||||
@@ -0,0 +1,12 @@
|
||||
name: 'Billboard.js Charts'
|
||||
type: module
|
||||
description: 'Charts module integration with Billboard.js Charts.'
|
||||
package: Charts
|
||||
core_version_requirement: ^10.3 || ^11
|
||||
dependencies:
|
||||
- 'charts:charts'
|
||||
|
||||
# Information added by Drupal.org packaging script on 2024-12-02
|
||||
version: '5.1.1'
|
||||
project: 'charts'
|
||||
datestamp: 1733170633
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Installation and uninstallation functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_requirements().
|
||||
*/
|
||||
function charts_billboard_requirements($phase) {
|
||||
$requirements = [];
|
||||
|
||||
switch ($phase) {
|
||||
case 'runtime':
|
||||
$library_path = charts_billboard_find_library();
|
||||
$config = \Drupal::config('charts.settings');
|
||||
$cdn = $config->get('advanced.requirements.cdn') ?? FALSE;
|
||||
|
||||
if (!$library_path) {
|
||||
if ($cdn) {
|
||||
$requirements['charts_billboard_js'] = [
|
||||
'title' => t('Billboard.js Library'),
|
||||
'value' => t('Available through a CDN'),
|
||||
'severity' => REQUIREMENT_WARNING,
|
||||
'description' => t('You are using the Billboard.js library via a content delivery network. It is generally considered better practice to install the library files locally. Please see the README file inside charts_billboard for instructions to install the library.'),
|
||||
];
|
||||
}
|
||||
else {
|
||||
$requirements['charts_billboard_js'] = [
|
||||
'title' => t('Billboard.js Library'),
|
||||
'value' => t('Not Installed'),
|
||||
'severity' => REQUIREMENT_ERROR,
|
||||
'description' => t('You are missing the Billboard.js library in your Drupal installation directory and you have opted not to use a CDN. Please either enable use of the CDN in the Chart Settings under the Advanced tab or see the README file inside charts_billboard for instructions to install the library.'),
|
||||
];
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If the library directory contains a demo directory, warn.
|
||||
if (file_exists($library_path . '/src')) {
|
||||
$requirements['charts_billboard_js'] = [
|
||||
'title' => t('Billboard.js Library'),
|
||||
'value' => t('Installed'),
|
||||
'severity' => REQUIREMENT_WARNING,
|
||||
'description' => t('You have installed the Billboard.js library in your Drupal installation directory, but it contains additional files that are not required and may be harmful. Please delete everything except the files listed in charts_billboard.libraries.yml. We have included a suggested post-install/post-update script in the README file.'),
|
||||
];
|
||||
}
|
||||
else {
|
||||
$requirements['charts_billboard_js'] = [
|
||||
'title' => t('Billboard.js Library'),
|
||||
'value' => t('Installed'),
|
||||
'severity' => REQUIREMENT_OK,
|
||||
'description' => t('You have installed the Billboard.js library in your Drupal installation directory.'),
|
||||
];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the location of the Billboard.js library.
|
||||
*
|
||||
* @return string
|
||||
* The location of the library, or FALSE if the library isn't installed.
|
||||
*/
|
||||
function charts_billboard_find_library() {
|
||||
// The following logic is taken from libraries_get_libraries()
|
||||
$searchdir = [];
|
||||
|
||||
// Similar to 'modules' and 'themes' directories inside an installation
|
||||
// profile, installation profiles may want to place libraries into a
|
||||
// 'libraries' directory.
|
||||
$searchdir[] = 'profiles/' . \Drupal::installProfile() . '/libraries';
|
||||
|
||||
// Always search libraries.
|
||||
$searchdir[] = 'libraries';
|
||||
|
||||
// Also search sites/<domain>/*.
|
||||
$container = \Drupal::getContainer();
|
||||
$site_path = $container->getParameter('site.path');
|
||||
$site_path = explode('/', $site_path);
|
||||
$site_name = $site_path[1];
|
||||
$searchdir[] = $site_name . '/libraries';
|
||||
|
||||
foreach ($searchdir as $dir) {
|
||||
if (file_exists($dir . '/billboard/dist/billboard.min.js') || file_exists($dir . '/billboard/dist/billboard.js')) {
|
||||
return $dir . '/billboard';
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
36
web/modules/charts/modules/charts_billboard/charts_billboard.libraries.yml
Executable file
36
web/modules/charts/modules/charts_billboard/charts_billboard.libraries.yml
Executable file
@@ -0,0 +1,36 @@
|
||||
charts_billboard:
|
||||
version: 1.x
|
||||
js:
|
||||
js/charts_billboard.js: { weight: -1 }
|
||||
dependencies:
|
||||
- charts/global
|
||||
- core/drupal
|
||||
- core/once
|
||||
d3:
|
||||
remote: 'https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js'
|
||||
version: 7.8.5
|
||||
license:
|
||||
name: BSD
|
||||
url: 'https://en.wikipedia.org/wiki/BSD_licenses'
|
||||
gpl-compatible: false
|
||||
cdn:
|
||||
/libraries/d3/: https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/
|
||||
js:
|
||||
/libraries/d3/d3.min.js: { minified: true }
|
||||
billboard:
|
||||
remote: 'https://naver.github.io/billboard.js/release/3.10.3/dist/billboard.min.js'
|
||||
version: 3.10.3
|
||||
license:
|
||||
name: MIT
|
||||
url: 'https://opensource.org/licenses/MIT'
|
||||
gpl-compatible: true
|
||||
cdn:
|
||||
/libraries/billboard/: https://naver.github.io/billboard.js/release/3.10.3/
|
||||
css:
|
||||
theme:
|
||||
/libraries/billboard/dist/billboard.min.css: { minified: true }
|
||||
js:
|
||||
/libraries/billboard/dist/billboard.min.js: { minified: true }
|
||||
dependencies:
|
||||
- charts_billboard/charts_billboard
|
||||
- charts_billboard/d3
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Implement module related hooks for the charts_billboard module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_charts_version3_to_new_settings_structure_alter().
|
||||
*/
|
||||
function charts_billboard_charts_version3_to_new_settings_structure_alter(array &$new_settings, string $for) {
|
||||
$config_factory = \Drupal::configFactory();
|
||||
$billboard_config = $config_factory->getEditable('charts_billboard.settings');
|
||||
if ($billboard_config) {
|
||||
$billboard_config->delete();
|
||||
}
|
||||
}
|
||||
49
web/modules/charts/modules/charts_billboard/composer.json
Executable file
49
web/modules/charts/modules/charts_billboard/composer.json
Executable file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "drupal/charts_billboard",
|
||||
"description": "Charts module integration with Billboard.js Charts.",
|
||||
"type": "drupal-module",
|
||||
"homepage": "http://drupal.org/project/charts",
|
||||
"support": {
|
||||
"issues": "https://www.drupal.org/project/issues/charts"
|
||||
},
|
||||
"license": "GPL-2.0+",
|
||||
"require": {
|
||||
"billboardjs/billboard": "3.10.3",
|
||||
"d3/d3": "7.8.5"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "billboardjs/billboard",
|
||||
"version": "3.10.3",
|
||||
"type": "drupal-library",
|
||||
"extra": {
|
||||
"installer-name": "billboard"
|
||||
},
|
||||
"dist": {
|
||||
"url": "https://github.com/naver/billboard.js/archive/3.10.3.zip",
|
||||
"type": "zip"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "d3/d3",
|
||||
"version": "7.8.5",
|
||||
"type": "drupal-library",
|
||||
"extra": {
|
||||
"installer-name": "d3"
|
||||
},
|
||||
"dist": {
|
||||
"url": "https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js",
|
||||
"type": "file"
|
||||
},
|
||||
"require": {
|
||||
"composer/installers": "^1.0 || ^2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
26
web/modules/charts/modules/charts_billboard/js/charts_billboard.js
Executable file
26
web/modules/charts/modules/charts_billboard/js/charts_billboard.js
Executable file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* @file
|
||||
* JavaScript integration between Billboard and Drupal.
|
||||
*/
|
||||
(function (Drupal, once) {
|
||||
|
||||
'use strict';
|
||||
|
||||
Drupal.behaviors.chartsBillboard = {
|
||||
attach: function (context, settings) {
|
||||
const contents = new Drupal.Charts.Contents();
|
||||
once('charts-billboard', '.charts-billboard', context).forEach(function (element) {
|
||||
const config = contents.getData(element.id);
|
||||
const title = config.title.text;
|
||||
// If the title contains '\\n', convert it to a line break.
|
||||
if (title.indexOf('\\n') !== -1) {
|
||||
config.title.text = title.replace(/\\n/g, '\n');
|
||||
}
|
||||
bb.generate(config);
|
||||
if (element.nextElementSibling && element.nextElementSibling.hasAttribute('data-charts-debug-container')) {
|
||||
element.nextElementSibling.querySelector('code').innerText = JSON.stringify(config, null, ' ');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}(Drupal, once));
|
||||
@@ -0,0 +1,454 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\charts_billboard\Plugin\chart\Library;
|
||||
|
||||
use Drupal\charts\Plugin\chart\Library\ChartBase;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Render\ElementInfoManagerInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Define a concrete class for a Chart.
|
||||
*
|
||||
* @Chart(
|
||||
* id = "billboard",
|
||||
* name = @Translation("Billboard.js"),
|
||||
* types = {
|
||||
* "area",
|
||||
* "bar",
|
||||
* "bubble",
|
||||
* "column",
|
||||
* "donut",
|
||||
* "gauge",
|
||||
* "line",
|
||||
* "pie",
|
||||
* "scatter",
|
||||
* "spline",
|
||||
* },
|
||||
* )
|
||||
*/
|
||||
class Billboard extends ChartBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The element info manager.
|
||||
*
|
||||
* @var \Drupal\Core\Render\ElementInfoManagerInterface
|
||||
*/
|
||||
protected $elementInfo;
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\views\Plugin\Block\ViewsBlockBase object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info
|
||||
* The element info manager.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, ElementInfoManagerInterface $element_info) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->elementInfo = $element_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('element_info')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::buildConfigurationForm($form, $form_state);
|
||||
$form['intro_text'] = [
|
||||
'#markup' => $this->t('This is a placeholder for Billboard.js-specific library options. If you would like to help build this out, please work from <a href="@issue_link">this issue</a>.', [
|
||||
'@issue_link' => Url::fromUri('https://www.drupal.org/project/charts/issues/3046983')->toString(),
|
||||
]),
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preRender(array $element) {
|
||||
// Populate chart settings.
|
||||
$chart_definition = [];
|
||||
|
||||
$chart_definition = $this->populateOptions($element, $chart_definition);
|
||||
$chart_definition = $this->populateData($element, $chart_definition);
|
||||
$chart_definition = $this->populateAxes($element, $chart_definition);
|
||||
|
||||
if (!empty($element['#height']) || !empty($element['#width'])) {
|
||||
$element['#attributes']['style'] = 'height:' . $element['#height'] . $element['#height_units'] . ';width:' . $element['#width'] . $element['#width_units'] . ';';
|
||||
}
|
||||
|
||||
if (!isset($element['#id'])) {
|
||||
$element['#id'] = Html::getUniqueId('chart-billboard');
|
||||
}
|
||||
$chart_definition['bindto'] = '#' . $element['#id'];
|
||||
|
||||
$element['#attached']['library'][] = 'charts_billboard/billboard';
|
||||
$element['#attributes']['class'][] = 'charts-billboard charts-bb';
|
||||
$element['#chart_definition'] = $chart_definition;
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the chart type.
|
||||
*
|
||||
* @param string $chart_type
|
||||
* The chart type.
|
||||
* @param bool $is_polar
|
||||
* Whether the polar is checked.
|
||||
*
|
||||
* @return string
|
||||
* The chart type.
|
||||
*/
|
||||
protected function getType($chart_type, $is_polar = FALSE) {
|
||||
// If Polar is checked, then convert to Radar chart type.
|
||||
if ($is_polar) {
|
||||
$type = 'radar';
|
||||
}
|
||||
else {
|
||||
$type = $chart_type == 'column' ? 'bar' : $chart_type;
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get options.
|
||||
*
|
||||
* @param string $type
|
||||
* The chart type.
|
||||
* @param array $element
|
||||
* The element.
|
||||
*
|
||||
* @return array
|
||||
* The returned options.
|
||||
*/
|
||||
protected function getOptionsByType($type, array $element) {
|
||||
$options = $this->getOptionsByCustomProperty($element, $type);
|
||||
if ($type === 'bar') {
|
||||
$options['width'] = $element['#width'];
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the options by custom property.
|
||||
*
|
||||
* @param array $element
|
||||
* The element.
|
||||
* @param string $type
|
||||
* The chart type.
|
||||
*
|
||||
* @return array
|
||||
* The return options.
|
||||
*/
|
||||
protected function getOptionsByCustomProperty(array $element, $type) {
|
||||
$options = [];
|
||||
$properties = Element::properties($element);
|
||||
// Remove properties which are not related to this chart type.
|
||||
$properties = array_filter($properties, function ($property) use ($type) {
|
||||
$query = '#chart_' . $type . '_';
|
||||
return substr($property, 0, strlen($query)) === $query;
|
||||
});
|
||||
foreach ($properties as $property) {
|
||||
$query = '#chart_' . $type . '_';
|
||||
$option_key = substr($property, strlen($query), strlen($property));
|
||||
$options[$option_key] = $element[$property];
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate options.
|
||||
*
|
||||
* @param array $element
|
||||
* The element.
|
||||
* @param array $chart_definition
|
||||
* The chart definition.
|
||||
*
|
||||
* @return array
|
||||
* Return the chart definition.
|
||||
*/
|
||||
private function populateOptions(array $element, array $chart_definition) {
|
||||
$type = $this->getType($element['#chart_type'], $element['#polar'] ?? FALSE);
|
||||
$title = $element['#title'] ?? '';
|
||||
if (!empty($element['#subtitle'])) {
|
||||
$title .= '\n' . $element['#subtitle'];
|
||||
}
|
||||
$chart_definition['title']['text'] = $title;
|
||||
$chart_definition['legend']['show'] = !empty($element['#legend_position']);
|
||||
if (!in_array($type, ['scatter', 'bubble'])) {
|
||||
$chart_definition['axis']['x']['type'] = 'category';
|
||||
}
|
||||
$chart_definition['data']['labels'] = (bool) $element['#data_labels'];
|
||||
|
||||
if ($type === 'pie' || $type === 'donut') {
|
||||
// Do nothing.
|
||||
}
|
||||
elseif ($type === 'gauge') {
|
||||
$chart_definition['gauge']['min'] = $element['#gauge']['min'];
|
||||
$chart_definition['gauge']['max'] = $element['#gauge']['max'];
|
||||
$chart_definition['color']['pattern'] = [
|
||||
'red',
|
||||
'yellow',
|
||||
'green',
|
||||
];
|
||||
$chart_definition['color']['threshold']['values'] = [
|
||||
$element['#gauge']['red_from'],
|
||||
$element['#gauge']['yellow_from'],
|
||||
$element['#gauge']['green_from'],
|
||||
];
|
||||
}
|
||||
elseif ($type === 'line' || $type === 'spline') {
|
||||
$chart_definition['point']['show'] = !empty($element['#data_markers']);
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* Billboard does not use bar, so column must be used. Since 'column'
|
||||
* is changed
|
||||
* to 'bar' in getType(), we need to use the value from the element.
|
||||
*/
|
||||
if ($element['#chart_type'] === 'bar') {
|
||||
$chart_definition['axis']['rotated'] = TRUE;
|
||||
}
|
||||
elseif ($element['#chart_type'] === 'column') {
|
||||
$type = 'bar';
|
||||
$chart_definition['axis']['rotated'] = FALSE;
|
||||
}
|
||||
}
|
||||
$chart_definition['data']['type'] = $type;
|
||||
// Merge in chart raw options.
|
||||
if (!empty($element['#raw_options'])) {
|
||||
$chart_definition = NestedArray::mergeDeepArray([
|
||||
$chart_definition,
|
||||
$element['#raw_options'],
|
||||
]);
|
||||
}
|
||||
|
||||
return $chart_definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate axes.
|
||||
*
|
||||
* @param array $element
|
||||
* The element.
|
||||
* @param array $chart_definition
|
||||
* The chart definition.
|
||||
*
|
||||
* @return array
|
||||
* Return the chart definition.
|
||||
*/
|
||||
private function populateAxes(array $element, array $chart_definition) {
|
||||
$children = Element::children($element);
|
||||
foreach ($children as $child) {
|
||||
$type = $element[$child]['#type'];
|
||||
if ($type === 'chart_xaxis') {
|
||||
$chart_definition['axis']['x']['label'] = $element[$child]['#title'] ?? '';
|
||||
$chart_type = $this->getType($element['#chart_type']);
|
||||
$categories = $this->stripLabelTags($element[$child]['#labels']);
|
||||
if (!in_array($chart_type, ['pie', 'donut'])) {
|
||||
if ($chart_type === 'scatter' || $chart_type === 'bubble') {
|
||||
// Do nothing.
|
||||
}
|
||||
else {
|
||||
$chart_definition['data']['columns'][] = ['x'];
|
||||
$chart_definition['data']['x'] = 'x';
|
||||
$categories_keys = array_keys($chart_definition['data']['columns']);
|
||||
$categories_key = end($categories_keys);
|
||||
foreach ($categories as $category) {
|
||||
$chart_definition['data']['columns'][$categories_key][] = $category;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$chart_definition['data']['columns'] = array_map(NULL, $categories, $chart_definition['data']['columns']);
|
||||
}
|
||||
}
|
||||
if ($type === 'chart_yaxis') {
|
||||
if (!empty($element[$child]['#opposite']) && $element[$child]['#opposite'] === TRUE) {
|
||||
$chart_definition['axis']['y2']['show'] = TRUE;
|
||||
$this->setLabelMinMax($chart_definition, 'y2', $element[$child]);
|
||||
}
|
||||
else {
|
||||
$this->setLabelMinMax($chart_definition, 'y', $element[$child]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $chart_definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the label, min, and max.
|
||||
*
|
||||
* @param array $chart_definition
|
||||
* The chart definition.
|
||||
* @param string $axis
|
||||
* The axis.
|
||||
* @param array $element
|
||||
* The element.
|
||||
*/
|
||||
private function setLabelMinMax(array &$chart_definition, string $axis, array $element): void {
|
||||
$chart_definition['axis'][$axis]['label'] = $element['#title'] ?? '';
|
||||
if (!empty($element['#min'])) {
|
||||
$chart_definition['axis'][$axis]['min'] = $element['#min'];
|
||||
}
|
||||
if (!empty($element['#max'])) {
|
||||
$chart_definition['axis'][$axis]['max'] = $element['#max'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate data.
|
||||
*
|
||||
* @param array $element
|
||||
* The element.
|
||||
* @param array $chart_definition
|
||||
* The chart definition.
|
||||
*
|
||||
* @return array
|
||||
* Return the chart definition.
|
||||
*/
|
||||
private function populateData(array &$element, array $chart_definition) {
|
||||
$type = $this->getType($element['#chart_type'], $element['#polar'] ?? FALSE);
|
||||
$types = [];
|
||||
$children = Element::children($element);
|
||||
$y_axes = [];
|
||||
foreach ($children as $child) {
|
||||
$element_type = $element[$child]['#type'];
|
||||
if ($element_type === 'chart_yaxis') {
|
||||
$y_axes[] = $child;
|
||||
}
|
||||
}
|
||||
$data_elements = array_filter($children, function ($child) use ($element) {
|
||||
return $element[$child]['#type'] === 'chart_data';
|
||||
});
|
||||
|
||||
$columns = $chart_definition['data']['columns'] ?? [];
|
||||
$column_keys = array_keys($columns);
|
||||
$columns_key_start = $columns ? end($column_keys) + 1 : 0;
|
||||
foreach ($data_elements as $key) {
|
||||
$child_element = $element[$key];
|
||||
// Make sure defaults are loaded.
|
||||
if (empty($child_element['#defaults_loaded'])) {
|
||||
$child_element += $this->elementInfo->getInfo($child_element['#type']);
|
||||
}
|
||||
if ($child_element['#color'] && $type !== 'gauge') {
|
||||
$chart_definition['color']['pattern'][] = $child_element['#color'];
|
||||
}
|
||||
if (!in_array($type, ['pie', 'donut'])) {
|
||||
$series_title = isset($child_element['#title']) ? strip_tags($child_element['#title']) : '';
|
||||
$types[$series_title] = $child_element['#chart_type'] ? $this->getType($child_element['#chart_type'], $element['#polar'] ?? FALSE) : $type;
|
||||
if (!in_array($type, ['scatter', 'bubble'])) {
|
||||
$columns[$columns_key_start][] = $series_title;
|
||||
foreach ($child_element['#data'] as $datum) {
|
||||
if (gettype($datum) === 'array') {
|
||||
if ($type === 'gauge') {
|
||||
array_shift($datum);
|
||||
}
|
||||
$columns[$columns_key_start][] = array_map(function ($item) {
|
||||
return isset($item) ? strip_tags($item) : NULL;
|
||||
}, $datum);
|
||||
}
|
||||
else {
|
||||
$columns[$columns_key_start][] = isset($datum) ? strip_tags($datum) : NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$row = [];
|
||||
$row[$series_title][0] = $series_title;
|
||||
$row[$series_title . '_x'][0] = $series_title . '_x';
|
||||
foreach ($child_element['#data'] as $datum) {
|
||||
$row[$series_title][] = $datum[0];
|
||||
$row[$series_title . '_x'][] = $datum[1];
|
||||
}
|
||||
$chart_definition['data']['xs'][$series_title] = $series_title . '_x';
|
||||
foreach ($row as $value) {
|
||||
$columns[] = $value;
|
||||
}
|
||||
$columns = array_values($columns);
|
||||
}
|
||||
}
|
||||
else {
|
||||
foreach ($child_element['#data'] as $datum_index => $datum) {
|
||||
if (!empty($datum['color'])) {
|
||||
$chart_definition['color']['pattern'][$datum_index] = $datum['color'];
|
||||
unset($datum['color']);
|
||||
$datum = array_values($datum);
|
||||
}
|
||||
$columns[] = $datum;
|
||||
}
|
||||
|
||||
// Add colors for each segment.
|
||||
foreach ($child_element['#grouping_colors'] ?? [] as $key => $colors_array) {
|
||||
$color = reset($colors_array);
|
||||
$chart_definition['color']['pattern'][$key] = $color;
|
||||
}
|
||||
}
|
||||
|
||||
$columns_key_start++;
|
||||
}
|
||||
if ($element['#stacking']) {
|
||||
$chart_definition['data']['groups'] = [array_keys($types)];
|
||||
}
|
||||
$chart_definition['data']['types'] = $types;
|
||||
$chart_definition['data']['columns'] = $columns;
|
||||
|
||||
if (count($y_axes) >= 2) {
|
||||
foreach ($columns as $index => $column) {
|
||||
if ($index <= 1) {
|
||||
$axis = ($index + 1) === 2 ? 2 : '';
|
||||
$chart_definition['data']['axes'][$column[0]] = 'y' . $axis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $chart_definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip tags from each item in an array.
|
||||
*
|
||||
* @param array $items
|
||||
* The array.
|
||||
*
|
||||
* @return array
|
||||
* Return the cleaned array.
|
||||
*/
|
||||
private function stripLabelTags(array $items): array {
|
||||
if (empty($items)) {
|
||||
return [];
|
||||
}
|
||||
$categories = [];
|
||||
foreach ($items as $item) {
|
||||
$categories[] = isset($item) ? strip_tags($item) : NULL;
|
||||
}
|
||||
|
||||
return $categories;
|
||||
}
|
||||
|
||||
}
|
||||
9
web/modules/charts/modules/charts_blocks/README.md
Normal file
9
web/modules/charts/modules/charts_blocks/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
#Charts Blocks
|
||||
|
||||
Use this module if you would like a chart added via Layout Builder or the
|
||||
"Block layout" page. You will need to enable at least one charting provider
|
||||
submodule before this module can be used. Data can be added manually or by
|
||||
uploading a CSV file.
|
||||
|
||||
If you create your chart using a View, you do not need this module - just add
|
||||
a "Block" display to your View.
|
||||
@@ -0,0 +1,12 @@
|
||||
name: Charts Blocks
|
||||
type: module
|
||||
description: Create Charts blocks without the need for Views.
|
||||
core_version_requirement: ^10.3 || ^11
|
||||
package: Charts
|
||||
dependencies:
|
||||
- drupal:charts
|
||||
|
||||
# Information added by Drupal.org packaging script on 2024-12-02
|
||||
version: '5.1.1'
|
||||
project: 'charts'
|
||||
datestamp: 1733170633
|
||||
24
web/modules/charts/modules/charts_blocks/charts_blocks.module
Executable file
24
web/modules/charts/modules/charts_blocks/charts_blocks.module
Executable file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains chart_block_example.module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function charts_blocks_help($route_name, RouteMatchInterface $route_match) {
|
||||
switch ($route_name) {
|
||||
// Main module help for the charts_blocks module.
|
||||
case 'help.page.charts_blocks':
|
||||
$output = '';
|
||||
$output .= '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>' . t('Create Charts blocks without the need for Views.') . '</p>';
|
||||
return $output;
|
||||
|
||||
default:
|
||||
}
|
||||
}
|
||||
114
web/modules/charts/modules/charts_blocks/src/Plugin/Block/ChartsBlock.php
Executable file
114
web/modules/charts/modules/charts_blocks/src/Plugin/Block/ChartsBlock.php
Executable file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\charts_blocks\Plugin\Block;
|
||||
|
||||
use Drupal\charts\Element\Chart;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Component\Uuid\UuidInterface;
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a 'ChartsBlock' block.
|
||||
*
|
||||
* @Block(
|
||||
* id = "charts_block",
|
||||
* admin_label = @Translation("Charts block"),
|
||||
* )
|
||||
*/
|
||||
class ChartsBlock extends BlockBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The config factory service.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* The UUID service.
|
||||
*
|
||||
* @var \Drupal\Component\Uuid\UuidInterface
|
||||
*/
|
||||
protected $uuidService;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, ConfigFactoryInterface $config_factory, UuidInterface $uuidService) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->setConfiguration($configuration);
|
||||
$this->configFactory = $config_factory;
|
||||
$this->uuidService = $uuidService;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('config.factory'),
|
||||
$container->get('uuid')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockForm($form, FormStateInterface $form_state) {
|
||||
parent::blockForm($form, $form_state);
|
||||
|
||||
$chart_block_configurations = !empty($this->configuration['chart']) ? $this->configuration['chart'] : [];
|
||||
|
||||
// Merge the charts default settings with this block's configuration.
|
||||
$charts_settings = $this->configFactory->get('charts.settings');
|
||||
$charts_default_settings = $charts_settings->get('charts_default_settings') ?? [];
|
||||
$defaults = NestedArray::mergeDeep($charts_default_settings, $chart_block_configurations);
|
||||
|
||||
$form['chart'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Chart configurations'),
|
||||
'#open' => TRUE,
|
||||
];
|
||||
|
||||
$form['chart']['settings'] = [
|
||||
'#type' => 'charts_settings',
|
||||
'#used_in' => 'basic_form',
|
||||
'#required' => TRUE,
|
||||
'#series' => TRUE,
|
||||
'#default_value' => $defaults,
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockSubmit($form, FormStateInterface $form_state) {
|
||||
$this->configuration['chart'] = $form_state->getValue(['chart', 'settings']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build() {
|
||||
$chart_settings = !empty($this->configuration['chart']) ? $this->configuration['chart'] : [];
|
||||
|
||||
// Creates a UUID for the chart ID.
|
||||
$chart_id = 'charts_block__' . $this->configuration['id'];
|
||||
$id = 'chart-' . $this->uuidService->generate();
|
||||
$build = Chart::buildElement($chart_settings, $chart_id);
|
||||
$build['#id'] = $id;
|
||||
$build['#chart_id'] = $chart_id;
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
||||
137
web/modules/charts/modules/charts_c3/README.md
Executable file
137
web/modules/charts/modules/charts_c3/README.md
Executable file
@@ -0,0 +1,137 @@
|
||||
#Installation Using Composer (recommended)
|
||||
|
||||
If you use Composer to manage dependencies, edit your site's "composer.json"
|
||||
file as follows.
|
||||
|
||||
1. Run the following command to ensure that you have the "composer/installers"
|
||||
package installed. This package facilitates the installation of packages into
|
||||
directories other than "/vendor" (e.g. "/libraries") using Composer.
|
||||
|
||||
composer require --prefer-dist composer/installers
|
||||
|
||||
2. Add the following to the "installer-paths" section of "composer.json":
|
||||
|
||||
"libraries/{$name}": ["type:drupal-library"],
|
||||
|
||||
3. Add the following to the "repositories" section of "composer.json":
|
||||
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "c3js/c3",
|
||||
"version": "v0.7.20",
|
||||
"type": "drupal-library",
|
||||
"extra": {
|
||||
"installer-name": "c3"
|
||||
},
|
||||
"dist": {
|
||||
"url": "https://github.com/c3js/c3/archive/v0.7.20.zip",
|
||||
"type": "zip"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "d3/d3",
|
||||
"version": "v4.9.1",
|
||||
"type": "drupal-library",
|
||||
"extra": {
|
||||
"installer-name": "d3"
|
||||
},
|
||||
"dist": {
|
||||
"url": "https://github.com/d3/d3/archive/v4.9.1.zip",
|
||||
"type": "zip"
|
||||
},
|
||||
"require": {
|
||||
"composer/installers": "^1.0 || ^2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4. This and the following step is optional but recommended. The reason for
|
||||
them is that when installing the C3.js package with Composer,
|
||||
additional files are added into the library directory. These files are not
|
||||
necessary and can be potentially harmful to your site, so it's best to remove
|
||||
them. So: create a new directory in your project root called "scripts".
|
||||
5. Inside that directory, create a new file called "clean-c3js.sh" and
|
||||
paste the following into it:
|
||||
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
declare -a directories=(
|
||||
"web/libraries/c3/.circleci"
|
||||
"web/libraries/c3/.github"
|
||||
"web/libraries/c3/data"
|
||||
"web/libraries/c3/design"
|
||||
"web/libraries/c3/docs"
|
||||
"web/libraries/c3/extensions"
|
||||
"web/libraries/c3/htdocs"
|
||||
"web/libraries/c3/spec"
|
||||
"web/libraries/c3/src"
|
||||
)
|
||||
counter=0
|
||||
echo "Deleting unneeded directories inside web/libraries/c3"
|
||||
for directory in "${directories[@]}"
|
||||
do
|
||||
if [ -d $directory ]; then
|
||||
echo "Deleting $directory"
|
||||
rm -rf $directory
|
||||
counter=$((counter+1))
|
||||
fi
|
||||
done
|
||||
echo "$counter folders were deleted"
|
||||
declare -a files=(
|
||||
"web/libraries/c3/CONTRIBUTING.md"
|
||||
"web/libraries/c3/README.md"
|
||||
"web/libraries/c3/LICENSE"
|
||||
"web/libraries/c3/package.json"
|
||||
"web/libraries/c3/.bmp.yml"
|
||||
"web/libraries/c3/.editorconfig"
|
||||
"web/libraries/c3/.gitignore"
|
||||
"web/libraries/c3/.jshintrc"
|
||||
"web/libraries/c3/.prettierrc.json"
|
||||
"web/libraries/c3/.travis.yml"
|
||||
"web/libraries/c3/bower.json"
|
||||
"web/libraries/c3/codecov.yml"
|
||||
"web/libraries/c3/component.json"
|
||||
"web/libraries/c3/config.rb"
|
||||
"web/libraries/c3/Gemfile"
|
||||
"web/libraries/c3/Gemfile.lock"
|
||||
"web/libraries/c3/karma.conf.js"
|
||||
"web/libraries/c3/MAINTAINANCE.md"
|
||||
"web/libraries/c3/rollup.config.js"
|
||||
"web/libraries/c3/tsconfig.json"
|
||||
"web/libraries/c3/yarn.lock"
|
||||
)
|
||||
counter=0
|
||||
echo "Deleting unneeded files inside web/libraries/c3"
|
||||
for file in "${files[@]}"
|
||||
do
|
||||
if [[ -f $file ]]; then
|
||||
echo "Deleting $file"
|
||||
rm $file
|
||||
counter=$((counter+1))
|
||||
fi
|
||||
done
|
||||
echo "$counter files were deleted"
|
||||
|
||||
6. Add a "scripts" entry to your "composer.json" file as shown below. If
|
||||
"scripts" already exists, you will need to do a little more to incorporate
|
||||
the code below.
|
||||
|
||||
"scripts": {
|
||||
"clean-c3js": "chmod +x scripts/clean-c3js.sh &&
|
||||
./scripts/clean-c3js.sh",
|
||||
"post-install-cmd": [
|
||||
"@clean-c3js"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"@clean-c3js"
|
||||
]
|
||||
}
|
||||
|
||||
7. Run the following command; you should find that new directories have been
|
||||
created under "/libraries".
|
||||
|
||||
composer require --prefer-dist c3js/c3:0.7.20 d3/d3:4.9.1
|
||||
12
web/modules/charts/modules/charts_c3/charts_c3.info.yml
Normal file
12
web/modules/charts/modules/charts_c3/charts_c3.info.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
name: 'C3.js Charts'
|
||||
type: module
|
||||
description: 'Charts module integration with C3.js Charts.'
|
||||
package: Charts
|
||||
core_version_requirement: ^10.3 || ^11
|
||||
dependencies:
|
||||
- 'charts:charts'
|
||||
|
||||
# Information added by Drupal.org packaging script on 2024-12-02
|
||||
version: '5.1.1'
|
||||
project: 'charts'
|
||||
datestamp: 1733170633
|
||||
95
web/modules/charts/modules/charts_c3/charts_c3.install
Normal file
95
web/modules/charts/modules/charts_c3/charts_c3.install
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Installation and uninstallation functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_requirements().
|
||||
*/
|
||||
function charts_c3_requirements($phase) {
|
||||
$requirements = [];
|
||||
|
||||
switch ($phase) {
|
||||
case 'runtime':
|
||||
$library_path = charts_c3_find_library();
|
||||
$config = \Drupal::config('charts.settings');
|
||||
$cdn = $config->get('advanced.requirements.cdn') ?? FALSE;
|
||||
|
||||
if (!$library_path) {
|
||||
if ($cdn) {
|
||||
$requirements['charts_c3_js'] = [
|
||||
'title' => t('C3.js Library'),
|
||||
'value' => t('Available through a CDN'),
|
||||
'severity' => REQUIREMENT_WARNING,
|
||||
'description' => t('You are using the C3.js library via a content delivery network. It is generally considered better practice to install the library files locally. Please see the README file inside charts_c3 for instructions to install the library.'),
|
||||
];
|
||||
}
|
||||
else {
|
||||
$requirements['charts_c3_js'] = [
|
||||
'title' => t('C3.js Library'),
|
||||
'value' => t('Not Installed'),
|
||||
'severity' => REQUIREMENT_ERROR,
|
||||
'description' => t('You are missing the C3.js library in your Drupal installation directory and you have opted not to use a CDN. Please either enable use of the CDN in the Chart Settings under the Advanced tab or see the README file inside charts_c3 for instructions to install the library.'),
|
||||
];
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If the library directory contains a demo directory, warn.
|
||||
if (file_exists($library_path . '/src')) {
|
||||
$requirements['charts_c3_js'] = [
|
||||
'title' => t('C3.js Library'),
|
||||
'value' => t('Installed'),
|
||||
'severity' => REQUIREMENT_WARNING,
|
||||
'description' => t('You have installed the C3.js library in your Drupal installation directory, but it contains additional files that are not required and may be harmful. Please delete everything except the files listed in charts_c3.libraries.yml. We have included a suggested post-install/post-update script in the README file.'),
|
||||
];
|
||||
}
|
||||
else {
|
||||
$requirements['charts_c3_js'] = [
|
||||
'title' => t('C3.js Library'),
|
||||
'value' => t('Installed'),
|
||||
'severity' => REQUIREMENT_OK,
|
||||
'description' => t('You have installed the C3.js library in your Drupal installation directory.'),
|
||||
];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the location of the C3.js library.
|
||||
*
|
||||
* @return string
|
||||
* The location of the library, or FALSE if the library isn't installed.
|
||||
*/
|
||||
function charts_c3_find_library() {
|
||||
// The following logic is taken from libraries_get_libraries()
|
||||
$searchdir = [];
|
||||
|
||||
// Similar to 'modules' and 'themes' directories inside an installation
|
||||
// profile, installation profiles may want to place libraries into a
|
||||
// 'libraries' directory.
|
||||
$searchdir[] = 'profiles/' . \Drupal::installProfile() . '/libraries';
|
||||
|
||||
// Always search libraries.
|
||||
$searchdir[] = 'libraries';
|
||||
|
||||
// Also search sites/<domain>/*.
|
||||
$container = \Drupal::getContainer();
|
||||
$site_path = $container->getParameter('site.path');
|
||||
$site_path = explode('/', $site_path);
|
||||
$site_name = $site_path[1];
|
||||
$searchdir[] = $site_name . '/libraries';
|
||||
|
||||
foreach ($searchdir as $dir) {
|
||||
if (file_exists($dir . '/c3/c3.min.js') || file_exists($dir . '/c3/c3.js')) {
|
||||
return $dir . '/c3';
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
36
web/modules/charts/modules/charts_c3/charts_c3.libraries.yml
Executable file
36
web/modules/charts/modules/charts_c3/charts_c3.libraries.yml
Executable file
@@ -0,0 +1,36 @@
|
||||
charts_c3:
|
||||
version: 1.x
|
||||
js:
|
||||
js/charts_c3.js: { weight: -1 }
|
||||
dependencies:
|
||||
- charts/global
|
||||
- core/drupal
|
||||
- core/once
|
||||
d3:
|
||||
remote: 'https://cdnjs.cloudflare.com/ajax/libs/d3/5.16.0/d3.min.js'
|
||||
version: 5.16.0
|
||||
license:
|
||||
name: BSD
|
||||
url: 'https://en.wikipedia.org/wiki/BSD_licenses'
|
||||
gpl-compatible: false
|
||||
cdn:
|
||||
/libraries/d3/: https://cdnjs.cloudflare.com/ajax/libs/d3/5.16.0/
|
||||
js:
|
||||
/libraries/d3/d3.min.js: { }
|
||||
c3:
|
||||
remote: 'https://cdnjs.cloudflare.com/ajax/libs/c3/0.7.20/c3.min.js'
|
||||
version: 0.7.20
|
||||
license:
|
||||
name: MIT
|
||||
url: 'https://opensource.org/licenses/MIT'
|
||||
gpl-compatible: true
|
||||
cdn:
|
||||
/libraries/c3/: https://cdnjs.cloudflare.com/ajax/libs/c3/0.7.20/
|
||||
css:
|
||||
theme:
|
||||
/libraries/c3/c3.min.css: { }
|
||||
js:
|
||||
/libraries/c3/c3.min.js: { }
|
||||
dependencies:
|
||||
- charts_c3/charts_c3
|
||||
- charts_c3/d3
|
||||
17
web/modules/charts/modules/charts_c3/charts_c3.module
Normal file
17
web/modules/charts/modules/charts_c3/charts_c3.module
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Implement module related hooks for the charts_c3 module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_charts_version3_to_new_settings_structure_alter().
|
||||
*/
|
||||
function charts_c3_charts_version3_to_new_settings_structure_alter(array &$new_settings, string $for) {
|
||||
$config_factory = \Drupal::configFactory();
|
||||
$c3_config = $config_factory->getEditable('charts_c3.settings');
|
||||
if ($c3_config) {
|
||||
$c3_config->delete();
|
||||
}
|
||||
}
|
||||
49
web/modules/charts/modules/charts_c3/composer.json
Executable file
49
web/modules/charts/modules/charts_c3/composer.json
Executable file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "drupal/charts_c3",
|
||||
"description": "Charts module integration with C3 Charts.",
|
||||
"type": "drupal-module",
|
||||
"homepage": "http://drupal.org/project/charts",
|
||||
"support": {
|
||||
"issues": "https://www.drupal.org/project/issues/charts"
|
||||
},
|
||||
"license": "GPL-2.0+",
|
||||
"require": {
|
||||
"c3js/c3": "0.7.20",
|
||||
"d3/d3": "4.9.1"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "c3js/c3",
|
||||
"version": "v0.7.20",
|
||||
"type": "drupal-library",
|
||||
"extra": {
|
||||
"installer-name": "c3"
|
||||
},
|
||||
"dist": {
|
||||
"url": "https://github.com/c3js/c3/archive/v0.7.20.zip",
|
||||
"type": "zip"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "d3/d3",
|
||||
"version": "v4.9.1",
|
||||
"type": "drupal-library",
|
||||
"extra": {
|
||||
"installer-name": "d3"
|
||||
},
|
||||
"dist": {
|
||||
"url": "https://github.com/d3/d3/archive/v4.9.1.zip",
|
||||
"type": "zip"
|
||||
},
|
||||
"require": {
|
||||
"composer/installers": "^1.0 || ^2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
21
web/modules/charts/modules/charts_c3/js/charts_c3.js
Executable file
21
web/modules/charts/modules/charts_c3/js/charts_c3.js
Executable file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @file
|
||||
* JavaScript's integration between C3 and Drupal.
|
||||
*/
|
||||
(function (Drupal, once) {
|
||||
|
||||
'use strict';
|
||||
|
||||
Drupal.behaviors.chartsC3 = {
|
||||
attach: function (context, settings) {
|
||||
const contents = new Drupal.Charts.Contents();
|
||||
once('charts-c3', '.charts-c3', context).forEach(function (element) {
|
||||
const config = contents.getData(element.id);
|
||||
c3.generate(config);
|
||||
if (element.nextElementSibling && element.nextElementSibling.hasAttribute('data-charts-debug-container')) {
|
||||
element.nextElementSibling.querySelector('code').innerText = JSON.stringify(config, null, ' ');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}(Drupal, once));
|
||||
454
web/modules/charts/modules/charts_c3/src/Plugin/chart/Library/C3.php
Executable file
454
web/modules/charts/modules/charts_c3/src/Plugin/chart/Library/C3.php
Executable file
@@ -0,0 +1,454 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\charts_c3\Plugin\chart\Library;
|
||||
|
||||
use Drupal\charts\Plugin\chart\Library\ChartBase;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Render\ElementInfoManagerInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Define a concrete class for a Chart.
|
||||
*
|
||||
* @Chart(
|
||||
* id = "c3",
|
||||
* name = @Translation("C3"),
|
||||
* types = {
|
||||
* "area",
|
||||
* "bar",
|
||||
* "bubble",
|
||||
* "column",
|
||||
* "donut",
|
||||
* "gauge",
|
||||
* "line",
|
||||
* "pie",
|
||||
* "scatter",
|
||||
* "spline",
|
||||
* },
|
||||
* )
|
||||
*/
|
||||
class C3 extends ChartBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The element info manager.
|
||||
*
|
||||
* @var \Drupal\Core\Render\ElementInfoManagerInterface
|
||||
*/
|
||||
protected $elementInfo;
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\views\Plugin\Block\ViewsBlockBase object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info
|
||||
* The element info manager.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, ElementInfoManagerInterface $element_info) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->elementInfo = $element_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('element_info')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::buildConfigurationForm($form, $form_state);
|
||||
$form['intro_text'] = [
|
||||
'#markup' => $this->t('<p>This is a placeholder for C3.js-specific library options. If you would like to help build this out, please work from <a href="@issue_link">this issue</a>.</p>', [
|
||||
'@issue_link' => Url::fromUri('https://www.drupal.org/project/charts/issues/3046982')->toString(),
|
||||
]),
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preRender(array $element) {
|
||||
// Populate chart settings.
|
||||
$chart_definition = [];
|
||||
|
||||
$chart_definition = $this->populateOptions($element, $chart_definition);
|
||||
$chart_definition = $this->populateData($element, $chart_definition);
|
||||
$chart_definition = $this->populateAxes($element, $chart_definition);
|
||||
|
||||
if (!empty($element['#height']) || !empty($element['#width'])) {
|
||||
$element['#attributes']['style'] = 'height:' . $element['#height'] . $element['#height_units'] . ';width:' . $element['#width'] . $element['#width_units'] . ';';
|
||||
}
|
||||
|
||||
if (!isset($element['#id'])) {
|
||||
$element['#id'] = Html::getUniqueId('chart-c3');
|
||||
}
|
||||
$chart_definition['bindto'] = '#' . $element['#id'];
|
||||
|
||||
$element['#attached']['library'][] = 'charts_c3/c3';
|
||||
$element['#attributes']['class'][] = 'charts-c3';
|
||||
$element['#chart_definition'] = $chart_definition;
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* The chart type.
|
||||
*
|
||||
* @param string $chart_type
|
||||
* The chart type.
|
||||
* @param bool $is_polar
|
||||
* Whether polar is checked.
|
||||
*
|
||||
* @return string
|
||||
* Return the type.
|
||||
*/
|
||||
protected function getType($chart_type, $is_polar = FALSE) {
|
||||
// If Polar is checked, then convert to Radar chart type.
|
||||
if ($is_polar) {
|
||||
$type = 'radar';
|
||||
}
|
||||
else {
|
||||
$type = $chart_type == 'column' ? 'bar' : $chart_type;
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get options by type.
|
||||
*
|
||||
* @param string $type
|
||||
* The chart type.
|
||||
* @param array $element
|
||||
* The element.
|
||||
*
|
||||
* @return array
|
||||
* Return options.
|
||||
*/
|
||||
protected function getOptionsByType($type, array $element) {
|
||||
$options = $this->getOptionsByCustomProperty($element, $type);
|
||||
if ($type === 'bar') {
|
||||
$options['width'] = $element['#width'];
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get options by custom property.
|
||||
*
|
||||
* @param array $element
|
||||
* The element.
|
||||
* @param string $type
|
||||
* The chart type.
|
||||
*
|
||||
* @return array
|
||||
* Return options.
|
||||
*/
|
||||
protected function getOptionsByCustomProperty(array $element, $type) {
|
||||
$options = [];
|
||||
$properties = Element::properties($element);
|
||||
// Remove properties which are not related to this chart type.
|
||||
$properties = array_filter($properties, function ($property) use ($type) {
|
||||
$query = '#chart_' . $type . '_';
|
||||
return substr($property, 0, strlen($query)) === $query;
|
||||
});
|
||||
foreach ($properties as $property) {
|
||||
$query = '#chart_' . $type . '_';
|
||||
$option_key = substr($property, strlen($query), strlen($property));
|
||||
$options[$option_key] = $element[$property];
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate options.
|
||||
*
|
||||
* @param array $element
|
||||
* The element.
|
||||
* @param array $chart_definition
|
||||
* The chart definition.
|
||||
*
|
||||
* @return array
|
||||
* Return the chart definition.
|
||||
*/
|
||||
private function populateOptions(array $element, array $chart_definition) {
|
||||
$type = $this->getType($element['#chart_type']);
|
||||
$title = $element['#title'] ?? '';
|
||||
if (!empty($element['#subtitle'])) {
|
||||
$title .= ': ' . $element['#subtitle'];
|
||||
}
|
||||
$chart_definition['title']['text'] = $title;
|
||||
$chart_definition['legend']['show'] = !empty($element['#legend_position']);
|
||||
if (!in_array($type, ['scatter', 'bubble'])) {
|
||||
$chart_definition['axis']['x']['type'] = 'category';
|
||||
}
|
||||
$chart_definition['data']['labels'] = (bool) $element['#data_labels'];
|
||||
|
||||
if ($type === 'pie' || $type === 'donut') {
|
||||
|
||||
}
|
||||
elseif ($type === 'gauge') {
|
||||
$chart_definition['gauge']['min'] = $element['#gauge']['min'];
|
||||
$chart_definition['gauge']['max'] = $element['#gauge']['max'];
|
||||
$chart_definition['color']['pattern'] = [
|
||||
'red',
|
||||
'yellow',
|
||||
'green',
|
||||
];
|
||||
$chart_definition['color']['threshold']['values'] = [
|
||||
$element['#gauge']['red_from'],
|
||||
$element['#gauge']['yellow_from'],
|
||||
$element['#gauge']['green_from'],
|
||||
];
|
||||
}
|
||||
elseif ($type === 'line' || $type === 'spline') {
|
||||
$chart_definition['point']['show'] = !empty($element['#data_markers']);
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* Billboard does not use bar, so column must be used. Since 'column'
|
||||
* is changed
|
||||
* to 'bar' in getType(), we need to use the value from the element.
|
||||
*/
|
||||
if ($element['#chart_type'] === 'bar') {
|
||||
$chart_definition['axis']['rotated'] = TRUE;
|
||||
}
|
||||
elseif ($element['#chart_type'] === 'column') {
|
||||
$type = 'bar';
|
||||
$chart_definition['axis']['rotated'] = FALSE;
|
||||
}
|
||||
}
|
||||
$chart_definition['data']['type'] = $type;
|
||||
// Merge in chart raw options.
|
||||
if (!empty($element['#raw_options'])) {
|
||||
$chart_definition = NestedArray::mergeDeepArray([
|
||||
$chart_definition,
|
||||
$element['#raw_options'],
|
||||
]);
|
||||
}
|
||||
|
||||
return $chart_definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate axes.
|
||||
*
|
||||
* @param array $element
|
||||
* The element.
|
||||
* @param array $chart_definition
|
||||
* The chart definition.
|
||||
*
|
||||
* @return array
|
||||
* Return chart definition.
|
||||
*/
|
||||
private function populateAxes(array $element, array $chart_definition) {
|
||||
$children = Element::children($element);
|
||||
foreach ($children as $child) {
|
||||
$type = $element[$child]['#type'];
|
||||
if ($type === 'chart_xaxis') {
|
||||
$chart_definition['axis']['x']['label'] = $element[$child]['#title'] ?? '';
|
||||
$chart_type = $this->getType($element['#chart_type']);
|
||||
$categories = $this->stripLabelTags($element[$child]['#labels']);
|
||||
if (!in_array($chart_type, ['pie', 'donut'])) {
|
||||
if ($chart_type === 'scatter' || $chart_type === 'bubble') {
|
||||
// Scatter is not supported: https://github.com/c3js/c3/issues/1902
|
||||
}
|
||||
else {
|
||||
$chart_definition['data']['columns'][] = ['x'];
|
||||
$chart_definition['data']['x'] = 'x';
|
||||
$categories_keys = array_keys($chart_definition['data']['columns']);
|
||||
$categories_key = end($categories_keys);
|
||||
foreach ($categories as $category) {
|
||||
$chart_definition['data']['columns'][$categories_key][] = $category;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$chart_definition['data']['columns'] = array_map(NULL, $categories, $chart_definition['data']['columns']);
|
||||
}
|
||||
}
|
||||
if ($type === 'chart_yaxis') {
|
||||
if (!empty($element[$child]['#opposite']) && $element[$child]['#opposite'] === TRUE) {
|
||||
$chart_definition['axis']['y2']['show'] = TRUE;
|
||||
$this->setLabelMinMax($chart_definition, 'y2', $element[$child]);
|
||||
}
|
||||
else {
|
||||
$this->setLabelMinMax($chart_definition, 'y', $element[$child]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $chart_definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the label, min, and max.
|
||||
*
|
||||
* @param array $chart_definition
|
||||
* The chart definition.
|
||||
* @param string $axis
|
||||
* The axis.
|
||||
* @param array $element
|
||||
* The element.
|
||||
*/
|
||||
private function setLabelMinMax(array &$chart_definition, string $axis, array $element): void {
|
||||
$chart_definition['axis'][$axis]['label'] = $element['#title'] ?? '';
|
||||
if (!empty($element['#min'])) {
|
||||
$chart_definition['axis'][$axis]['min'] = $element['#min'];
|
||||
}
|
||||
if (!empty($element['#max'])) {
|
||||
$chart_definition['axis'][$axis]['max'] = $element['#max'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate data.
|
||||
*
|
||||
* @param array $element
|
||||
* The element.
|
||||
* @param array $chart_definition
|
||||
* The chart definition.
|
||||
*
|
||||
* @return array
|
||||
* Return the chart definition.
|
||||
*/
|
||||
private function populateData(array &$element, array $chart_definition) {
|
||||
$type = $this->getType($element['#chart_type']);
|
||||
$types = [];
|
||||
$children = Element::children($element);
|
||||
$y_axes = [];
|
||||
foreach ($children as $child) {
|
||||
$element_type = $element[$child]['#type'];
|
||||
if ($element_type === 'chart_yaxis') {
|
||||
$y_axes[] = $child;
|
||||
}
|
||||
}
|
||||
$data_elements = array_filter($children, function ($child) use ($element) {
|
||||
return $element[$child]['#type'] === 'chart_data';
|
||||
});
|
||||
|
||||
$columns = $chart_definition['data']['columns'] ?? [];
|
||||
$column_keys = array_keys($columns);
|
||||
$columns_key_start = $columns ? end($column_keys) + 1 : 0;
|
||||
foreach ($data_elements as $key) {
|
||||
$child_element = $element[$key];
|
||||
// Make sure defaults are loaded.
|
||||
if (empty($child_element['#defaults_loaded'])) {
|
||||
$child_element += $this->elementInfo->getInfo($child_element['#type']);
|
||||
}
|
||||
if ($child_element['#color'] && $type !== 'gauge') {
|
||||
$chart_definition['color']['pattern'][] = $child_element['#color'];
|
||||
}
|
||||
if (!in_array($type, ['pie', 'donut'])) {
|
||||
$series_title = isset($child_element['#title']) ? strip_tags($child_element['#title']) : '';
|
||||
$types[$series_title] = $child_element['#chart_type'] ? $this->getType($child_element['#chart_type']) : $type;
|
||||
if (!in_array($type, ['scatter', 'bubble'])) {
|
||||
$columns[$columns_key_start][] = $series_title;
|
||||
foreach ($child_element['#data'] as $datum) {
|
||||
if (gettype($datum) === 'array') {
|
||||
if ($type === 'gauge') {
|
||||
array_shift($datum);
|
||||
}
|
||||
$columns[$columns_key_start][] = array_map(function ($item) {
|
||||
return isset($item) ? strip_tags($item) : NULL;
|
||||
}, $datum);
|
||||
}
|
||||
else {
|
||||
$columns[$columns_key_start][] = isset($datum) ? strip_tags($datum) : NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$row = [];
|
||||
$row[$series_title][0] = $series_title;
|
||||
$row[$series_title . '_x'][0] = $series_title . '_x';
|
||||
foreach ($child_element['#data'] as $datum) {
|
||||
$row[$series_title][] = $datum[0];
|
||||
$row[$series_title . '_x'][] = $datum[1];
|
||||
}
|
||||
$chart_definition['data']['xs'][$series_title] = $series_title . '_x';
|
||||
foreach ($row as $value) {
|
||||
$columns[] = $value;
|
||||
}
|
||||
$columns = array_values($columns);
|
||||
}
|
||||
}
|
||||
else {
|
||||
foreach ($child_element['#data'] as $datum_index => $datum) {
|
||||
if (!empty($datum['color'])) {
|
||||
$chart_definition['color']['pattern'][$datum_index] = $datum['color'];
|
||||
unset($datum['color']);
|
||||
$datum = array_values($datum);
|
||||
}
|
||||
$columns[] = $datum;
|
||||
}
|
||||
|
||||
// Add colors for each segment.
|
||||
foreach ($child_element['#grouping_colors'] ?? [] as $key => $colors_array) {
|
||||
$color = reset($colors_array);
|
||||
$chart_definition['color']['pattern'][$key] = $color;
|
||||
}
|
||||
}
|
||||
|
||||
$columns_key_start++;
|
||||
}
|
||||
if ($element['#stacking']) {
|
||||
$chart_definition['data']['groups'] = [array_keys($types)];
|
||||
}
|
||||
$chart_definition['data']['types'] = $types;
|
||||
$chart_definition['data']['columns'] = $columns;
|
||||
|
||||
if (count($y_axes) >= 2) {
|
||||
foreach ($columns as $index => $column) {
|
||||
if ($index <= 1) {
|
||||
$axis = ($index + 1) === 2 ? 2 : '';
|
||||
$chart_definition['data']['axes'][$column[0]] = 'y' . $axis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $chart_definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip tags from each item in an array.
|
||||
*
|
||||
* @param array $items
|
||||
* The array.
|
||||
*
|
||||
* @return array
|
||||
* Return the cleaned array.
|
||||
*/
|
||||
private function stripLabelTags(array $items): array {
|
||||
if (empty($items)) {
|
||||
return [];
|
||||
}
|
||||
$categories = [];
|
||||
foreach ($items as $item) {
|
||||
$categories[] = isset($item) ? strip_tags($item) : NULL;
|
||||
}
|
||||
|
||||
return $categories;
|
||||
}
|
||||
|
||||
}
|
||||
158
web/modules/charts/modules/charts_chartjs/README.md
Executable file
158
web/modules/charts/modules/charts_chartjs/README.md
Executable file
@@ -0,0 +1,158 @@
|
||||
#Installation Using Composer (recommended)
|
||||
|
||||
If you use Composer to manage dependencies, edit your site's "composer.json"
|
||||
file as follows.
|
||||
|
||||
1. Add the asset-packagist composer repository to "composer.json".
|
||||
This allows installing packages (like Chart.js) that are published on NPM.
|
||||
|
||||
"asset-packagist": {
|
||||
"type": "composer",
|
||||
"url": "https://asset-packagist.org"
|
||||
},
|
||||
|
||||
You may need to add it in your composer.json file like this (second item in
|
||||
the array):
|
||||
|
||||
"repositories": [
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "https://packages.drupal.org/8"
|
||||
},
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "https://asset-packagist.org"
|
||||
},
|
||||
],
|
||||
|
||||
2. Run the following command to ensure that you have the
|
||||
"oomphinc/composer-installers-extender" package installed. This package
|
||||
facilitates the installation of any package into directories other than the
|
||||
default "/vendor" (e.g. "/libraries") using Composer.
|
||||
|
||||
composer require --prefer-dist oomphinc/composer-installers-extender
|
||||
|
||||
3. Configure composer to install the Chart.js dependencies into "/libraries"
|
||||
by adding the following "installer-types" and "installer-paths" to the "extra"
|
||||
section of "composer.json". If you are not using the "web" directory, then
|
||||
remove "web/" from the lines below:
|
||||
|
||||
"extra": {
|
||||
"installer-types": ["npm-asset"],
|
||||
"installer-paths": {
|
||||
"web/libraries/chart.js": ["npm-asset/chart.js"],
|
||||
"web/libraries/chartjs-adapter-date-fns": [
|
||||
"npm-asset/chartjs-adapter-date-fns"
|
||||
],
|
||||
"web/libraries/chartjs-plugin-datalabels": [
|
||||
"npm-asset/chartjs-plugin-datalabels"
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
NOTE: If this isn't working, try instead adding:
|
||||
"extra": {
|
||||
"installer-types": ["npm-asset"],
|
||||
"installer-paths": {
|
||||
...
|
||||
"web/libraries/{$name}": ["type:drupal-library", "vendor:npm-asset"]
|
||||
},
|
||||
}
|
||||
|
||||
4. This and the following step is optional but recommended. The reason for
|
||||
them is that when installing the Chart.js package with Composer,
|
||||
additional files are added into the library directory. These files are not
|
||||
necessary and can be potentially harmful to your site, so it's best to remove
|
||||
them. So: create a new directory in your project root called "scripts".
|
||||
|
||||
5. Inside that directory, create a new file called "clean-chartjs.sh" and
|
||||
paste the following into it:
|
||||
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
declare -a directories=(
|
||||
"web/libraries/chart.js/auto"
|
||||
"web/libraries/chart.js/helpers"
|
||||
"web/libraries/chart.js/types"
|
||||
"web/libraries/chart.js/dist/chunks"
|
||||
"web/libraries/chart.js/dist/docs"
|
||||
"web/libraries/chart.js/dist/controllers"
|
||||
"web/libraries/chart.js/dist/core"
|
||||
"web/libraries/chart.js/dist/elements"
|
||||
"web/libraries/chart.js/dist/helpers"
|
||||
"web/libraries/chart.js/dist/platform"
|
||||
"web/libraries/chart.js/dist/plugins"
|
||||
"web/libraries/chart.js/dist/scales"
|
||||
"web/libraries/chart.js/dist/types"
|
||||
"web/libraries/chartjs-plugin-datalabels/types"
|
||||
)
|
||||
counter=0
|
||||
echo "Deleting unneeded directories inside web/libraries/chartjs"
|
||||
for directory in "${directories[@]}"
|
||||
do
|
||||
if [ -d $directory ]; then
|
||||
echo "Deleting $directory"
|
||||
rm -rf $directory
|
||||
counter=$((counter+1))
|
||||
fi
|
||||
done
|
||||
echo "$counter folders were deleted"
|
||||
declare -a files=(
|
||||
"web/libraries/chart.js/README.md"
|
||||
"web/libraries/chart.js/LICENSE.md"
|
||||
"web/libraries/chart.js/package.json"
|
||||
"web/libraries/chart.js/dist/helpers.esm.js"
|
||||
"web/libraries/chart.js/dist/helpers.mjs"
|
||||
"web/libraries/chart.js/dist/chart.mjs"
|
||||
"web/libraries/chart.js/dist/chart.esm.js"
|
||||
"web/libraries/chart.js/dist/chart.cjs"
|
||||
"web/libraries/chart.js/dist/chart.cjs.map"
|
||||
"web/libraries/chart.js/dist/helpers.js"
|
||||
"web/libraries/chart.js/dist/helpers.js.map"
|
||||
"web/libraries/chart.js/dist/helpers.cjs"
|
||||
"web/libraries/chart.js/dist/helpers.cjs.map"
|
||||
"web/libraries/chart.js/dist/index.d.ts"
|
||||
"web/libraries/chart.js/dist/index.umd.d.ts"
|
||||
"web/libraries/chart.js/dist/types.d.ts"
|
||||
"web/libraries/chartjs-adapter-date-fns/README.md"
|
||||
"web/libraries/chartjs-adapter-date-fns/LICENSE.md"
|
||||
"web/libraries/chartjs-adapter-date-fns/package.json"
|
||||
"web/libraries/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.esm.js"
|
||||
"web/libraries/chartjs-plugin-datalabels/README.md"
|
||||
"web/libraries/chartjs-plugin-datalabels/LICENSE.md"
|
||||
"web/libraries/chartjs-plugin-datalabels/package.json"
|
||||
"web/libraries/chartjs-plugin-datalabels/bower.json"
|
||||
)
|
||||
counter=0
|
||||
echo "Deleting unneeded files inside web/libraries/chartjs"
|
||||
for file in "${files[@]}"
|
||||
do
|
||||
if [[ -f $file ]]; then
|
||||
echo "Deleting $file"
|
||||
rm $file
|
||||
counter=$((counter+1))
|
||||
fi
|
||||
done
|
||||
echo "$counter files were deleted"
|
||||
|
||||
6. Add a "scripts" entry to your "composer.json" file as shown below. If
|
||||
"scripts" already exists, you will need to do a little more to incorporate
|
||||
the code below.
|
||||
|
||||
"scripts": {
|
||||
"clean-chartjs": "chmod +x scripts/clean-chartjs.sh &&
|
||||
./scripts/clean-chartjs.sh",
|
||||
"post-install-cmd": [
|
||||
"@clean-chartjs"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"@clean-chartjs"
|
||||
]
|
||||
}
|
||||
|
||||
7. Run the following command; you should find that new directories have been
|
||||
created under "/libraries".
|
||||
|
||||
composer require --prefer-dist npm-asset/chart.js:^4.4
|
||||
npm-asset/chartjs-adapter-date-fns:^3.0
|
||||
npm-asset/chartjs-plugin-datalabels:^2.0
|
||||
@@ -0,0 +1,5 @@
|
||||
polarArea:
|
||||
label: 'Polar Area'
|
||||
axis: 'y_only'
|
||||
axis_inverted: false
|
||||
stacking: false
|
||||
@@ -0,0 +1,12 @@
|
||||
name: 'Chart.js Charts'
|
||||
type: module
|
||||
description: 'Charts module integration with Chart.js.'
|
||||
package: Charts
|
||||
core_version_requirement: ^10.3 || ^11
|
||||
dependencies:
|
||||
- 'charts:charts'
|
||||
|
||||
# Information added by Drupal.org packaging script on 2024-12-02
|
||||
version: '5.1.1'
|
||||
project: 'charts'
|
||||
datestamp: 1733170633
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Installation and uninstallation functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_requirements().
|
||||
*/
|
||||
function charts_chartjs_requirements($phase) {
|
||||
$requirements = [];
|
||||
|
||||
switch ($phase) {
|
||||
case 'runtime':
|
||||
$library_path = charts_chartjs_find_library();
|
||||
$config = \Drupal::config('charts.settings');
|
||||
$cdn = $config->get('advanced.requirements.cdn') ?? FALSE;
|
||||
|
||||
if (!$library_path) {
|
||||
if ($cdn) {
|
||||
$requirements['charts_chartjs_js'] = [
|
||||
'title' => t('Chart.js Library'),
|
||||
'value' => t('Available through a CDN'),
|
||||
'severity' => REQUIREMENT_WARNING,
|
||||
'description' => t('You are using the Chart.js library via a content delivery network. It is generally considered better practice to install the library files locally. Please see the README file inside charts_chartjs for instructions to install the library.'),
|
||||
];
|
||||
}
|
||||
else {
|
||||
$requirements['charts_chartjs_js'] = [
|
||||
'title' => t('Chart.js Library'),
|
||||
'value' => t('Not Installed'),
|
||||
'severity' => REQUIREMENT_ERROR,
|
||||
'description' => t('You are missing the Chart.js library in your Drupal installation directory and you have opted not to use a CDN. Please either enable use of the CDN in the Chart Settings under the Advanced tab or see the README file inside charts_chartjs for instructions to install the library.'),
|
||||
];
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If the library directory contains "auto", provide a warning.
|
||||
if (file_exists($library_path . '/auto')) {
|
||||
$requirements['charts_chartjs_js'] = [
|
||||
'title' => t('Chart.js Library'),
|
||||
'value' => t('Installed'),
|
||||
'severity' => REQUIREMENT_WARNING,
|
||||
'description' => t('You have installed the Chart.js library in your Drupal installation directory, but it contains additional files that are not required and may be harmful. Please delete everything except the files listed in charts_chartjs.libraries.yml. We have included a suggested post-install/post-update script in the README file.'),
|
||||
];
|
||||
}
|
||||
else {
|
||||
$requirements['charts_chartjs_js'] = [
|
||||
'title' => t('Chart.js Library'),
|
||||
'value' => t('Installed'),
|
||||
'severity' => REQUIREMENT_OK,
|
||||
'description' => t('You have installed the Chart.js library in your Drupal installation directory.'),
|
||||
];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the location of the Chart.js library.
|
||||
*
|
||||
* @return string
|
||||
* The location of the library, or FALSE if the library isn't installed.
|
||||
*/
|
||||
function charts_chartjs_find_library() {
|
||||
// The following logic is taken from libraries_get_libraries()
|
||||
$searchdir = [];
|
||||
|
||||
// Similar to 'modules' and 'themes' directories inside an installation
|
||||
// profile, installation profiles may want to place libraries into a
|
||||
// 'libraries' directory.
|
||||
$searchdir[] = 'profiles/' . \Drupal::installProfile() . '/libraries';
|
||||
|
||||
// Always search libraries.
|
||||
$searchdir[] = 'libraries';
|
||||
|
||||
// Also search sites/<domain>/*.
|
||||
$container = \Drupal::getContainer();
|
||||
$site_path = $container->getParameter('site.path');
|
||||
$site_path = explode('/', $site_path);
|
||||
$site_name = $site_path[1];
|
||||
$searchdir[] = $site_name . '/libraries';
|
||||
|
||||
foreach ($searchdir as $dir) {
|
||||
if (file_exists($dir . '/chart.js/dist/chart.js') || file_exists($dir . '/chart.js/dist/chart.umd.js')) {
|
||||
return $dir . '/chartjs';
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
26
web/modules/charts/modules/charts_chartjs/charts_chartjs.libraries.yml
Executable file
26
web/modules/charts/modules/charts_chartjs/charts_chartjs.libraries.yml
Executable file
@@ -0,0 +1,26 @@
|
||||
charts_chartjs:
|
||||
remote: 'https://github.com/chartjs/Chart.js'
|
||||
version: VERSION
|
||||
license:
|
||||
name: 'Apache 2.0'
|
||||
url: 'https://www.apache.org/licenses/LICENSE-2.0'
|
||||
gpl-compatible: false
|
||||
# @todo
|
||||
cdn:
|
||||
/libraries/chart.js/: https://unpkg.com/chart.js@4.4.0/
|
||||
/libraries/chartjs-adapter-date-fns/: https://unpkg.com/chartjs-adapter-date-fns@3.0.0/
|
||||
/libraries/chartjs-plugin-datalabels/: https://unpkg.com/chartjs-plugin-datalabels@2.0.0/
|
||||
js:
|
||||
/libraries/chart.js/dist/chart.umd.js: { }
|
||||
/libraries/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.js: { }
|
||||
/libraries/chartjs-plugin-datalabels/dist/chartjs-plugin-datalabels.js: { }
|
||||
dependencies:
|
||||
- core/drupal
|
||||
- core/once
|
||||
chartjs:
|
||||
version: 1.x
|
||||
js:
|
||||
js/charts_chartjs.js: { weight: -1 }
|
||||
dependencies:
|
||||
- charts/global
|
||||
- charts_chartjs/charts_chartjs
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Implement module related hooks for the charts_chartjs module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_charts_version3_to_new_settings_structure_alter().
|
||||
*/
|
||||
function charts_chartjs_charts_version3_to_new_settings_structure_alter(array &$new_settings, string $for) {
|
||||
$config_factory = \Drupal::configFactory();
|
||||
$is_config = $for === 'config';
|
||||
$chartjs_config = $is_config ? $config_factory->getEditable('charts_chartjs.settings') : NULL;
|
||||
if (!$is_config || !$chartjs_config || empty($new_settings['library']) || $new_settings['library'] !== 'chartjs') {
|
||||
if ($chartjs_config) {
|
||||
$chartjs_config->delete();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$new_settings['library_config'] = [
|
||||
'xaxis' => [
|
||||
'autoskip' => TRUE,
|
||||
'horizontal_axis_title_align' => 'start',
|
||||
],
|
||||
'yaxis' => [
|
||||
'vertical_axis_title_align' => 'start',
|
||||
],
|
||||
];
|
||||
$chartjs_config->delete();
|
||||
}
|
||||
38
web/modules/charts/modules/charts_chartjs/composer.json
Executable file
38
web/modules/charts/modules/charts_chartjs/composer.json
Executable file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "drupal/charts_chartjs",
|
||||
"description": "Charts module integration with Chart.js Charts.",
|
||||
"type": "drupal-module",
|
||||
"homepage": "http://drupal.org/project/charts",
|
||||
"support": {
|
||||
"issues": "https://www.drupal.org/project/issues/charts"
|
||||
},
|
||||
"license": "GPL-2.0+",
|
||||
"repositories": {
|
||||
"asset-packagist": {
|
||||
"type": "composer",
|
||||
"url": "https://asset-packagist.org"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"oomphinc/composer-installers-extender": "^2",
|
||||
"npm-asset/chart.js": "^4.4",
|
||||
"npm-asset/chartjs-adapter-date-fns": "^3.0",
|
||||
"npm-asset/chartjs-plugin-datalabels": "^2.0"
|
||||
},
|
||||
"extra": {
|
||||
"installer-types": [
|
||||
"npm-asset"
|
||||
],
|
||||
"installer-paths": {
|
||||
"web/libraries/chartjs": [
|
||||
"npm-asset/chart.js"
|
||||
],
|
||||
"web/libraries/chartjs-adapter-date-fns": [
|
||||
"npm-asset/chartjs-adapter-date-fns"
|
||||
],
|
||||
"web/libraries/chartjs-plugin-datalabels": [
|
||||
"npm-asset/chartjs-plugin-datalabels"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
charts.settings.library_plugin.chartjs:
|
||||
type: charts_settings_library_plugin
|
||||
mapping:
|
||||
xaxis:
|
||||
label: 'X-Axis settings'
|
||||
type: mapping
|
||||
mapping:
|
||||
autoskip:
|
||||
label: 'Enable autoskip'
|
||||
type: boolean
|
||||
horizontal_axis_title_align:
|
||||
label: 'Horizontal axis title alignment'
|
||||
type: string
|
||||
yaxis:
|
||||
label: 'Y-Axis settings'
|
||||
type: mapping
|
||||
mapping:
|
||||
vertical_axis_title_align:
|
||||
label: 'Vertical axis title alignment'
|
||||
type: string
|
||||
51
web/modules/charts/modules/charts_chartjs/js/charts_chartjs.js
Executable file
51
web/modules/charts/modules/charts_chartjs/js/charts_chartjs.js
Executable file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @file
|
||||
* JavaScript integration between Chart.js and Drupal.
|
||||
*/
|
||||
(function (Drupal, once) {
|
||||
|
||||
'use strict';
|
||||
|
||||
function copyAttributes(source, target) {
|
||||
return Array.from(source.attributes).forEach(attribute => {
|
||||
target.setAttribute(
|
||||
attribute.nodeName === 'id' ? 'data-id' : attribute.nodeName,
|
||||
attribute.nodeValue,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Drupal.behaviors.chartsChartjs = {
|
||||
attach: function (context) {
|
||||
const contents = new Drupal.Charts.Contents();
|
||||
once('load-charts-chartjs', '.charts-chartjs', context).forEach(function (element) {
|
||||
const chartId = element.id;
|
||||
// Switching div for canvas element.
|
||||
const parent = element.parentNode;
|
||||
const canvas = document.createElement('canvas');
|
||||
// Transferring the attributes of our source element to the canvas.
|
||||
copyAttributes(element, canvas);
|
||||
canvas.id = chartId;
|
||||
parent.replaceChild(canvas, element);
|
||||
|
||||
// Initializing the chart item.
|
||||
const chart = contents.getData(chartId);
|
||||
const options = chart['options'];
|
||||
const enabled_plugins = [];
|
||||
// If options.plugins.dataLabels.display is set to true, we need to add the plugin to the chart.
|
||||
if (options.plugins && options.plugins.dataLabels && options.plugins.dataLabels.display) {
|
||||
enabled_plugins.push(ChartDataLabels);
|
||||
}
|
||||
new Chart(chartId, {
|
||||
type: chart['type'],
|
||||
data: chart['data'],
|
||||
plugins: enabled_plugins,
|
||||
options: options,
|
||||
});
|
||||
if (canvas.nextElementSibling && canvas.nextElementSibling.hasAttribute('data-charts-debug-container')) {
|
||||
canvas.nextElementSibling.querySelector('code').innerText = JSON.stringify(chart, null, ' ');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}(Drupal, once));
|
||||
659
web/modules/charts/modules/charts_chartjs/src/Plugin/chart/Library/Chartjs.php
Executable file
659
web/modules/charts/modules/charts_chartjs/src/Plugin/chart/Library/Chartjs.php
Executable file
@@ -0,0 +1,659 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\charts_chartjs\Plugin\chart\Library;
|
||||
|
||||
use Drupal\charts\Plugin\chart\Library\ChartBase;
|
||||
use Drupal\Component\Utility\Color;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Define a concrete class for a Chart.
|
||||
*
|
||||
* @Chart(
|
||||
* id = "chartjs",
|
||||
* name = @Translation("Chart.js"),
|
||||
* types = {
|
||||
* "area",
|
||||
* "bar",
|
||||
* "bubble",
|
||||
* "column",
|
||||
* "donut",
|
||||
* "line",
|
||||
* "pie",
|
||||
* "polarArea",
|
||||
* "scatter",
|
||||
* "spline",
|
||||
* },
|
||||
* )
|
||||
*/
|
||||
class Chartjs extends ChartBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [
|
||||
'xaxis' => [
|
||||
'autoskip' => TRUE,
|
||||
'horizontal_axis_title_align' => 'start',
|
||||
],
|
||||
'yaxis' => [
|
||||
'vertical_axis_title_align' => 'start',
|
||||
],
|
||||
] + parent::defaultConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::buildConfigurationForm($form, $form_state);
|
||||
|
||||
$form['intro_text'] = [
|
||||
'#markup' => $this->t('<p>This is a placeholder for Chart.js-specific library options. If you would like to add more chartjs specific settings, please work from <a href="@issue_link">this issue</a>.</p>', [
|
||||
'@issue_link' => Url::fromUri('https://www.drupal.org/project/charts/issues/3046984')->toString(),
|
||||
]),
|
||||
];
|
||||
|
||||
$xaxis_configuration = $this->configuration['xaxis'] ?? [];
|
||||
$form['xaxis'] = [
|
||||
'#title' => $this->t('X-Axis Settings'),
|
||||
'#type' => 'fieldset',
|
||||
'#tree' => TRUE,
|
||||
];
|
||||
$form['xaxis']['autoskip'] = [
|
||||
'#title' => $this->t('Enable autoskip'),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => !empty($xaxis_configuration['autoskip']),
|
||||
];
|
||||
$form['xaxis']['horizontal_axis_title_align'] = [
|
||||
'#title' => $this->t('Align horizontal axis title'),
|
||||
'#type' => 'select',
|
||||
'#options' => [
|
||||
'start' => $this->t('Start'),
|
||||
'center' => $this->t('Center'),
|
||||
'end' => $this->t('End'),
|
||||
],
|
||||
'#default_value' => $xaxis_configuration['horizontal_axis_title_align'] ?? 'center',
|
||||
];
|
||||
$form['yaxis'] = [
|
||||
'#title' => $this->t('Y-Axis Settings'),
|
||||
'#type' => 'fieldset',
|
||||
'#tree' => TRUE,
|
||||
];
|
||||
$form['yaxis']['vertical_axis_title_align'] = [
|
||||
'#title' => $this->t('Align vertical axis title'),
|
||||
'#type' => 'select',
|
||||
'#options' => [
|
||||
'start' => $this->t('Start'),
|
||||
'center' => $this->t('Center'),
|
||||
'end' => $this->t('End'),
|
||||
],
|
||||
'#default_value' => $this->configuration['yaxis']['vertical_axis_title_align'] ?? 'center',
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build configurations.
|
||||
*
|
||||
* @param array $form
|
||||
* The form element.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state.
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::submitConfigurationForm($form, $form_state);
|
||||
|
||||
if (!$form_state->getErrors()) {
|
||||
$values = $form_state->getValue($form['#parents']);
|
||||
$this->configuration['xaxis'] = $values['xaxis'];
|
||||
$this->configuration['yaxis'] = $values['yaxis'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preRender(array $element) {
|
||||
$chart_definition = [];
|
||||
|
||||
if (!isset($element['#id'])) {
|
||||
$element['#id'] = Html::getUniqueId('chartjs-render');
|
||||
}
|
||||
|
||||
if (!empty($element['#height']) || !empty($element['#width'])) {
|
||||
$element['#content_prefix']['manual_sizing'] = [
|
||||
'#type' => 'inline_template',
|
||||
'#template' => '<div data-chartjs-render-wrapper style="display:inline-block;height:' . $element['#height'] . $element['#height_units'] . ';width:' . $element['#width'] . $element['#width_units'] . ';">',
|
||||
];
|
||||
$element['#content_suffix']['manual_sizing'] = [
|
||||
'#type' => 'inline_template',
|
||||
'#template' => '</div>',
|
||||
];
|
||||
}
|
||||
|
||||
$chart_definition = $this->populateOptions($element, $chart_definition);
|
||||
$chart_definition = $this->populateCategories($element, $chart_definition);
|
||||
$chart_definition = $this->populateDatasets($element, $chart_definition);
|
||||
$chart_definition = $this->populateAxes($element, $chart_definition);
|
||||
|
||||
// Merge in chart raw options.
|
||||
if (!empty($element['#raw_options'])) {
|
||||
$chart_definition = NestedArray::mergeDeepArray([
|
||||
$chart_definition,
|
||||
$element['#raw_options'],
|
||||
]);
|
||||
}
|
||||
|
||||
$element['#attached']['library'][] = 'charts_chartjs/chartjs';
|
||||
$element['#attributes']['class'][] = 'charts-chartjs';
|
||||
$element['#chart_definition'] = $chart_definition;
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate chart axes.
|
||||
*
|
||||
* @param array $element
|
||||
* The element.
|
||||
* @param array $chart_definition
|
||||
* The chart definition.
|
||||
*
|
||||
* @return array
|
||||
* Return the chart definition.
|
||||
*/
|
||||
private function populateAxes(array $element, array $chart_definition) {
|
||||
$stacking = !empty($element['#stacking']) && $element['#stacking'] == 1;
|
||||
$chart_type = $chart_definition['type'];
|
||||
$children = Element::children($element);
|
||||
/*
|
||||
* Setting defaults based on what Views uses. However, API users may
|
||||
* have different keys for their X and Y axes.
|
||||
*/
|
||||
$axes_info = [];
|
||||
foreach ($children as $child) {
|
||||
$type = $element[$child]['#type'];
|
||||
if ($type === 'chart_xaxis') {
|
||||
$x_axis_key = $child;
|
||||
$xaxis_configuration = $this->configuration['xaxis'] ?? [];
|
||||
if (!in_array($chart_type, $this->getPieStyleTypes())) {
|
||||
if ($chart_type !== 'radar') {
|
||||
$chart_definition['options']['scales']['x'] = [
|
||||
'stacked' => $stacking,
|
||||
'ticks' => [
|
||||
'autoSkip' => $xaxis_configuration['autoskip'] ?? 1,
|
||||
'maxRotation' => $element[$x_axis_key]['#labels_rotation'] ?? 0,
|
||||
'minRotation' => $element[$x_axis_key]['#labels_rotation'] ?? 0,
|
||||
],
|
||||
];
|
||||
if (!empty($element[$x_axis_key]['#title'])) {
|
||||
$chart_definition['options']['scales']['x']['title']['display'] = TRUE;
|
||||
$chart_definition['options']['scales']['x']['title']['text'] = $element[$x_axis_key]['#title'];
|
||||
$chart_definition['options']['scales']['x']['title']['align'] = $xaxis_configuration['horizontal_axis_title_align'] ?? '';
|
||||
}
|
||||
}
|
||||
}
|
||||
$axes_info['x'] = [
|
||||
'element' => $element[$x_axis_key] ?? [],
|
||||
'config' => $this->configuration['xaxis'] ?? [],
|
||||
];
|
||||
}
|
||||
elseif ($type === 'chart_yaxis') {
|
||||
$target_axes = array_column($chart_definition['data']['datasets'],'yAxisID');
|
||||
$y_axis_key = in_array($child, $target_axes) ? $child : 'y';
|
||||
$axes_info[$y_axis_key] = [
|
||||
'element' => $element[$child] ?? [],
|
||||
'config' => $this->configuration['yaxis'] ?? [],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Build axes options in chart_definition.
|
||||
if (!in_array($chart_type, $this->getPieStyleTypes())) {
|
||||
if (!empty($element['#stacking']) && $element['#stacking'] == 1) {
|
||||
$stacking = TRUE;
|
||||
}
|
||||
else {
|
||||
$stacking = FALSE;
|
||||
}
|
||||
if ($chart_type !== 'radar') {
|
||||
$chart_definition['options']['scales']['x'] = [
|
||||
'stacked' => $stacking,
|
||||
'ticks' => [
|
||||
'autoSkip' => $axes_info['x']['config']['autoskip'] ?? 1,
|
||||
'maxRotation' => $axes_info['x']['element']['#labels_rotation'] ?? 0,
|
||||
'minRotation' => $axes_info['x']['element']['#labels_rotation'] ?? 0,
|
||||
],
|
||||
];
|
||||
|
||||
// Set configured values for each axis.
|
||||
foreach ($axes_info as $axis_id => $axis_info) {
|
||||
$axis_element = $axis_info['element'];
|
||||
$axis_config = $axis_info['config'];
|
||||
|
||||
if ($axis_element['#type'] == 'chart_yaxis') {
|
||||
$chart_definition['options']['scales'][$axis_id] = [
|
||||
'ticks' => [
|
||||
'beginAtZero' => NULL,
|
||||
'maxRotation' => $axes_info[$axis_id]['element']['#labels_rotation'] ?? 0,
|
||||
'minRotation' => $axes_info[$axis_id]['element']['#labels_rotation'] ?? 0,
|
||||
],
|
||||
'maxTicksLimit' => 11,
|
||||
'precision' => NULL,
|
||||
'stepSize' => NULL,
|
||||
'suggestedMax' => NULL,
|
||||
'suggestedMin' => NULL,
|
||||
'stacked' => $stacking,
|
||||
];
|
||||
}
|
||||
|
||||
// Set the axis type.
|
||||
if (!empty($axis_element['#axis_type'])) {
|
||||
$chart_definition['options']['scales'][$axis_id]['type'] = $axis_element['#axis_type'];
|
||||
}
|
||||
|
||||
// Set axis position.
|
||||
if (!empty($axis_element['#opposite'])) {
|
||||
$chart_definition['options']['scales'][$axis_id]['position'] = 'right';
|
||||
}
|
||||
|
||||
// Set min and max values.
|
||||
foreach (['min', 'max'] as $range_value) {
|
||||
if (isset($axis_element["#$range_value"])) {
|
||||
$chart_definition['options']['scales'][$axis_id][$range_value] = $axis_element["#$range_value"];
|
||||
}
|
||||
}
|
||||
|
||||
// Set title properties.
|
||||
if (!empty($axis_element['#title'])) {
|
||||
$chart_definition['options']['scales'][$axis_id]['title']['display'] = TRUE;
|
||||
$chart_definition['options']['scales'][$axis_id]['title']['text'] = $axis_element['#title'];
|
||||
|
||||
if (!empty($axis_config['vertical_axis_title_align'])) {
|
||||
$chart_definition['options']['scales'][$axis_id]['title']['align'] = $axis_config['vertical_axis_title_align'];
|
||||
}
|
||||
}
|
||||
|
||||
// Set title color.
|
||||
if (!empty($axis_element['#title_color'])) {
|
||||
$chart_definition['options']['scales'][$axis_id]['title']['color'] = $axis_element['#title_color'];
|
||||
}
|
||||
|
||||
// Set title font.
|
||||
foreach (['weight', 'style', 'size'] as $font_attribute) {
|
||||
$config_name = "#title_font_$font_attribute";
|
||||
if (!empty($axis_element[$config_name])) {
|
||||
$chart_definition['options']['scales'][$axis_id]['title']['font'][$font_attribute] = $axis_element[$config_name];
|
||||
}
|
||||
}
|
||||
|
||||
// Set tick color.
|
||||
foreach (['color', 'rotation'] as $tick_attribute) {
|
||||
$config_name = "#labels_$tick_attribute";
|
||||
if (!empty($axis_element[$config_name])) {
|
||||
$chart_definition['options']['scales'][$axis_id]['ticks'][$tick_attribute] = $axis_element[$config_name];
|
||||
}
|
||||
}
|
||||
|
||||
// Set tick font.
|
||||
foreach (['weight', 'style', 'size'] as $font_attribute) {
|
||||
$config_name = "#labels_font_$font_attribute";
|
||||
if (!empty($axis_element[$config_name])) {
|
||||
$chart_definition['options']['scales'][$axis_id]['ticks']['font'][$font_attribute] = $axis_element[$config_name];
|
||||
}
|
||||
}
|
||||
// Set grid line colors.
|
||||
if (!empty($axis_element['#grid_line_color'])) {
|
||||
$chart_definition['options']['scales'][$axis_id]['grid']['color'] = $axis_element['#grid_line_color'];
|
||||
}
|
||||
if (!empty($axis_element['#base_line_color'])) {
|
||||
$chart_definition['options']['scales'][$axis_id]['grid']['borderColor'] = $axis_element['#base_line_color'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $chart_definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate options.
|
||||
*
|
||||
* @param array $element
|
||||
* The element.
|
||||
* @param array $chart_definition
|
||||
* The chart definition.
|
||||
*
|
||||
* @return array
|
||||
* Return the chart definition.
|
||||
*/
|
||||
private function populateOptions(array $element, array $chart_definition) {
|
||||
$chart_type = $this->populateChartType($element);
|
||||
$chart_definition['type'] = $chart_type;
|
||||
|
||||
// Horizontal bar charts are configured by changing the bar chart indexAxis.
|
||||
// See https://chartjs.org/docs/latest/charts/bar.html#horizontal-bar-chart.
|
||||
if ($element['#chart_type'] === 'bar') {
|
||||
$chart_definition['options']['indexAxis'] = 'y';
|
||||
}
|
||||
|
||||
$chart_definition['options']['plugins']['title'] = $this->buildTitle($element);
|
||||
if (!empty($element['#subtitle'])) {
|
||||
$chart_definition['options']['plugins']['subtitle'] = [
|
||||
'display' => TRUE,
|
||||
'text' => $element['#subtitle'],
|
||||
];
|
||||
}
|
||||
$chart_definition['options']['plugins']['tooltip']['enabled'] = $element['#tooltips'];
|
||||
if (!empty($element['#data_labels'])) {
|
||||
$chart_definition['options']['plugins']['dataLabels']['display'] = TRUE;
|
||||
}
|
||||
$chart_definition['options']['plugins']['legend'] = $this->buildLegend($element);
|
||||
|
||||
return $chart_definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate categories.
|
||||
*
|
||||
* @param array $element
|
||||
* The element.
|
||||
* @param array $chart_definition
|
||||
* The chart definition.
|
||||
*
|
||||
* @return array
|
||||
* Return the chart definition.
|
||||
*/
|
||||
private function populateCategories(array $element, array $chart_definition) {
|
||||
$children = Element::children($element);
|
||||
$categories = [];
|
||||
foreach ($children as $child) {
|
||||
$type = $element[$child]['#type'];
|
||||
if ($type === 'chart_xaxis' && isset($element[$child]['#labels'])) {
|
||||
$categories = is_array($element[$child]['#labels']) ? array_map('strip_tags', $element[$child]['#labels']) : [];
|
||||
}
|
||||
if (in_array($element['#chart_type'], $this->getPieStyleTypes())
|
||||
&& $type !== 'chart_xaxis') {
|
||||
if ($element[$child]['#type'] === 'chart_data') {
|
||||
// Get the first item in each array inside $element[$child]['#data'].
|
||||
$categories = array_map(function ($item) {
|
||||
if (!empty($item['color'])) {
|
||||
unset($item['color']);
|
||||
}
|
||||
return gettype($item) === 'array' ? array_values($item) : $item;
|
||||
}, $element[$child]['#data']);
|
||||
}
|
||||
}
|
||||
// Merge in axis raw options.
|
||||
if (!empty($element[$child]['#raw_options'])) {
|
||||
$categories = NestedArray::mergeDeepArray([
|
||||
$categories,
|
||||
$element[$child]['#raw_options'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
$chart_definition['data']['labels'] = $categories;
|
||||
return $chart_definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate Dataset.
|
||||
*
|
||||
* @param array $element
|
||||
* The element.
|
||||
* @param array $chart_definition
|
||||
* The chart definition.
|
||||
*
|
||||
* @return array
|
||||
* Return the chart definition.
|
||||
*/
|
||||
private function populateDatasets(array $element, array $chart_definition) {
|
||||
$chart_type = $chart_definition['type'];
|
||||
$datasets = [];
|
||||
foreach (Element::children($element) as $key) {
|
||||
if ($element[$key]['#type'] === 'chart_data') {
|
||||
$series_data = [];
|
||||
$dataset = [];
|
||||
// Populate the data.
|
||||
foreach ($element[$key]['#data'] as $data_index => $data) {
|
||||
if (isset($series_data[$data_index])) {
|
||||
$series_data[$data_index][] = $data;
|
||||
}
|
||||
else {
|
||||
if ($chart_type === 'scatter') {
|
||||
$data = ['y' => $data[1], 'x' => $data[0]];
|
||||
}
|
||||
if ($chart_type === 'bubble') {
|
||||
/*
|
||||
* The radius is not scaled in Chart.js, so it can look very bad.
|
||||
* For suggestions about how to deal with this, see:
|
||||
* https://github.com/chartjs/Chart.js/issues/3355
|
||||
*/
|
||||
$data = ['y' => $data[1], 'x' => $data[0], 'r' => $data[2]];
|
||||
}
|
||||
// Convert the array from Views when using pie-type charts
|
||||
// and no label field.
|
||||
if (in_array($chart_type, $this->getPieStyleTypes()) && !empty($data['color'])) {
|
||||
$element['#colors'][$data_index] = $data['color'];
|
||||
unset($data['color']);
|
||||
$data = array_values($data);
|
||||
}
|
||||
/*
|
||||
* This is here to account for differences between Views and
|
||||
* the API. Will change if someone can find a better way.
|
||||
*/
|
||||
if (in_array($chart_type, $this->getPieStyleTypes()) && !empty($data[1])) {
|
||||
$data = $data[1];
|
||||
}
|
||||
$series_data[$data_index] = $data;
|
||||
}
|
||||
}
|
||||
if (!empty($element[$key]['#target_axis'])) {
|
||||
$dataset['yAxisID'] = $element[$key]['#target_axis'];
|
||||
}
|
||||
$dataset['label'] = $element[$key]['#title'];
|
||||
$dataset['data'] = $series_data;
|
||||
|
||||
// Set the background and border color.
|
||||
if (!empty($element[$key]['#color'])) {
|
||||
if (!in_array($chart_type, $this->getPieStyleTypes())) {
|
||||
$dataset['borderColor'] = $element[$key]['#color'];
|
||||
}
|
||||
$dataset['backgroundColor'] = $element[$key]['#color'];
|
||||
}
|
||||
if (in_array($chart_type, $this->getPieStyleTypes()) && !empty($element['#colors'])) {
|
||||
$dataset['backgroundColor'] = $element['#colors'];
|
||||
}
|
||||
|
||||
$series_type = isset($element[$key]['#chart_type']) ? $this->populateChartType($element[$key]) : $chart_type;
|
||||
$dataset['type'] = $series_type;
|
||||
if (!empty($element[$key]['#chart_type']) && $element[$key]['#chart_type'] === 'area') {
|
||||
$dataset['fill'] = 'origin';
|
||||
$dataset['backgroundColor'] = $this->getTranslucentColor($element[$key]['#color']);
|
||||
}
|
||||
elseif ($element['#chart_type'] === 'area') {
|
||||
$dataset['fill'] = 'origin';
|
||||
$dataset['backgroundColor'] = $this->getTranslucentColor($element[$key]['#color']);
|
||||
}
|
||||
else {
|
||||
$dataset['fill'] = FALSE;
|
||||
}
|
||||
|
||||
// Merge in dataset raw options.
|
||||
if (!empty($element[$key]['#raw_options'])) {
|
||||
$dataset = NestedArray::mergeDeepArray([
|
||||
$dataset,
|
||||
$element[$key]['#raw_options'],
|
||||
]);
|
||||
}
|
||||
|
||||
$datasets[] = $dataset;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$chart_definition['data']['datasets'] = $datasets;
|
||||
|
||||
return $chart_definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a type that can be used by Chart.js.
|
||||
*
|
||||
* @param array $element
|
||||
* The given element.
|
||||
*
|
||||
* @return string
|
||||
* The generated type.
|
||||
*/
|
||||
protected function populateChartType(array $element) {
|
||||
switch ($element['#chart_type']) {
|
||||
case 'bar':
|
||||
|
||||
case 'column':
|
||||
$type = 'bar';
|
||||
break;
|
||||
|
||||
case 'area':
|
||||
|
||||
case 'spline':
|
||||
$type = 'line';
|
||||
break;
|
||||
|
||||
case 'donut':
|
||||
$type = 'doughnut';
|
||||
break;
|
||||
|
||||
case 'gauge':
|
||||
// Gauge is currently not supported by Chart.js.
|
||||
$type = 'donut';
|
||||
break;
|
||||
|
||||
default:
|
||||
$type = $element['#chart_type'];
|
||||
break;
|
||||
}
|
||||
if (isset($element['#polar']) && $element['#polar'] == 1) {
|
||||
if ($element['#chart_type'] === 'area' || $element['#chart_type'] === 'polarArea') {
|
||||
$type = 'polarArea';
|
||||
}
|
||||
else {
|
||||
$type = 'radar';
|
||||
}
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds legend based on element properties.
|
||||
*
|
||||
* @param array $element
|
||||
* The element.
|
||||
*
|
||||
* @return array
|
||||
* The legend array.
|
||||
*/
|
||||
protected function buildLegend(array $element) {
|
||||
$legend = [];
|
||||
// Configure the legend display.
|
||||
$legend['display'] = (bool) $element['#legend'];
|
||||
|
||||
// Configure legend position.
|
||||
if (!empty($element['#legend_position'])) {
|
||||
$legend['position'] = $element['#legend_position'];
|
||||
if (!empty($element['#legend_font_weight'])) {
|
||||
$legend['labels']['font']['weight'] = $element['#legend_font_weight'];
|
||||
}
|
||||
if (!empty($element['#legend_font_style'])) {
|
||||
$legend['labels']['font']['style'] = $element['#legend_font_style'];
|
||||
}
|
||||
if (!empty($element['#legend_font_size'])) {
|
||||
$legend['labels']['font']['size'] = $element['#legend_font_size'];
|
||||
}
|
||||
}
|
||||
|
||||
return $legend;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds title based on element properties.
|
||||
*
|
||||
* @param array $element
|
||||
* The element.
|
||||
*
|
||||
* @return array
|
||||
* The title array.
|
||||
*/
|
||||
protected function buildTitle(array $element) {
|
||||
$title = [];
|
||||
if (!empty($element['#title'])) {
|
||||
$title = [
|
||||
'display' => TRUE,
|
||||
'text' => $element['#title'],
|
||||
];
|
||||
if (!empty($element['#title_position'])) {
|
||||
if (in_array($element['#title_position'], ['in', 'out'])) {
|
||||
$title['position'] = 'top';
|
||||
}
|
||||
else {
|
||||
$title['position'] = $element['#title_position'];
|
||||
}
|
||||
}
|
||||
if (!empty($element['#title_color'])) {
|
||||
$title['color'] = $element['#title_color'];
|
||||
}
|
||||
if (!empty($element['#title_font_weight'])) {
|
||||
$title['font']['weight'] = $element['#title_font_weight'];
|
||||
}
|
||||
if (!empty($element['#title_font_style'])) {
|
||||
$title['font']['style'] = $element['#title_font_style'];
|
||||
}
|
||||
if (!empty($element['#title_font_size'])) {
|
||||
$title['font']['size'] = $element['#title_font_size'];
|
||||
}
|
||||
}
|
||||
|
||||
return $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translucent color.
|
||||
*
|
||||
* @param string $color
|
||||
* The color.
|
||||
*
|
||||
* @return string
|
||||
* The color.
|
||||
*/
|
||||
protected function getTranslucentColor($color) {
|
||||
if (!$color) {
|
||||
return '';
|
||||
}
|
||||
$rgb = Color::hexToRgb($color);
|
||||
|
||||
return 'rgba(' . implode(",", $rgb) . ',' . 0.5 . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns pie-style chart types.
|
||||
*
|
||||
* @return array
|
||||
* An array of pie-style chart types.
|
||||
*/
|
||||
private static function getPieStyleTypes(): array {
|
||||
return [
|
||||
'pie',
|
||||
'doughnut',
|
||||
'polarArea',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\charts_chartjs\Kernel;
|
||||
|
||||
use Drupal\Tests\charts\Kernel\DimensionsTest as BaseDimensionsTest;
|
||||
|
||||
/**
|
||||
* Test the dimensions element property behavior for the chartjs library.
|
||||
*
|
||||
* @group charts
|
||||
*/
|
||||
class DimensionsTest extends BaseDimensionsTest {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'charts',
|
||||
'charts_chartjs',
|
||||
];
|
||||
|
||||
/**
|
||||
* Test that the set dimensions are correctly added on the element.
|
||||
*
|
||||
* @param array $element
|
||||
* The element to be tested.
|
||||
* @param string|null $expected_width
|
||||
* The expected width to be found on the element.
|
||||
* @param string|null $expected_height
|
||||
* The expected height to be found on the element.
|
||||
*
|
||||
* @dataProvider provideChartElements
|
||||
*/
|
||||
public function testDimensions(array $element, string $expected_width = NULL, string $expected_height = NULL) {
|
||||
$element['#chart_library'] = 'chartjs';
|
||||
$styles = $this->getChartStyle($element, '[data-chartjs-render-wrapper]');
|
||||
$width = $styles['width'] ?? NULL;
|
||||
$height = $styles['height'] ?? NULL;
|
||||
$this->assertEquals($expected_width, $width);
|
||||
$this->assertEquals($expected_height, $height);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\charts_chartjs\Kernel;
|
||||
|
||||
use Drupal\Tests\charts\Kernel\ChartsKernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests the raw_options element property behavior.
|
||||
*
|
||||
* @group charts
|
||||
*/
|
||||
class RawOptionsTest extends ChartsKernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'charts',
|
||||
'charts_chartjs',
|
||||
];
|
||||
|
||||
/**
|
||||
* Test that the raw options settings can override main definition.
|
||||
*/
|
||||
public function testRawOptionsOverride() {
|
||||
$element = [
|
||||
'#type' => 'chart',
|
||||
'#chart_type' => 'bar',
|
||||
'#stacking' => 1,
|
||||
'#raw_options' => [
|
||||
'options' => [
|
||||
'scales' => [
|
||||
'x' => ['stacked' => FALSE],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$path = ['options', 'scales', 'x', 'stacked'];
|
||||
$this->assertJsonPropertyHasValue($element, $path, FALSE);
|
||||
}
|
||||
|
||||
}
|
||||
42
web/modules/charts/modules/charts_google/README.md
Executable file
42
web/modules/charts/modules/charts_google/README.md
Executable file
@@ -0,0 +1,42 @@
|
||||
#Installation using Composer (recommended)
|
||||
|
||||
If you use Composer to manage dependencies, edit your site's "composer.json"
|
||||
file as follows.
|
||||
|
||||
1. Run `composer require --prefer-dist composer/installers` to ensure that
|
||||
you have the "composer/installers" package installed. This package
|
||||
facilitates the installation of packages into directories other than
|
||||
"/vendor" (e.g. "/libraries") using Composer.
|
||||
|
||||
2. Add the following to the "installer-paths" section of "composer.json":
|
||||
|
||||
"libraries/{$name}": ["type:drupal-library"],
|
||||
|
||||
3. Add the following to the "repositories" section of "composer.json":
|
||||
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "google/charts",
|
||||
"version": "45",
|
||||
"type": "drupal-library",
|
||||
"extra": {
|
||||
"installer-name": "google_charts"
|
||||
},
|
||||
"dist": {
|
||||
"url": "https://www.gstatic.com/charts/loader.js",
|
||||
"type": "file"
|
||||
},
|
||||
"require": {
|
||||
"composer/installers": "^1.0 || ^2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4. Run `composer require --prefer-dist google/charts:45` - you should find
|
||||
that new directories have been created under "/libraries"
|
||||
|
||||
**Please note**: if you observe an SSL error when trying to download this
|
||||
library, you can address this by changing the "url" in the code above to
|
||||
"http://www.gstatic.com/charts/loader.js" and by adding in:
|
||||
"secure-http": false
|
||||
5
web/modules/charts/modules/charts_google/charts_google.charts_types.yml
Executable file
5
web/modules/charts/modules/charts_google/charts_google.charts_types.yml
Executable file
@@ -0,0 +1,5 @@
|
||||
table:
|
||||
label: 'Table'
|
||||
axis: 'xy'
|
||||
axis_inverted: false
|
||||
stacking: false
|
||||
@@ -0,0 +1,12 @@
|
||||
name: 'Google Charts'
|
||||
type: module
|
||||
description: 'Charts module integration with Google Charts.'
|
||||
package: Charts
|
||||
core_version_requirement: ^10.3 || ^11
|
||||
dependencies:
|
||||
- 'charts:charts'
|
||||
|
||||
# Information added by Drupal.org packaging script on 2024-12-02
|
||||
version: '5.1.1'
|
||||
project: 'charts'
|
||||
datestamp: 1733170633
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Installation and uninstallation functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_requirements().
|
||||
*/
|
||||
function charts_google_requirements($phase) {
|
||||
$requirements = [];
|
||||
|
||||
switch ($phase) {
|
||||
case 'runtime':
|
||||
$library_path = charts_google_find_library();
|
||||
|
||||
if (!$library_path) {
|
||||
$requirements['charts_google_js'] = [
|
||||
'title' => t('Google Charts Library'),
|
||||
'value' => t('Not Installed'),
|
||||
'severity' => REQUIREMENT_WARNING,
|
||||
'description' => t('You are missing the Google Charts library in your Drupal installation directory. Please see the README file inside charts_google for instructions to install the library.'),
|
||||
];
|
||||
}
|
||||
else {
|
||||
$requirements['charts_google_js'] = [
|
||||
'title' => t('Google Charts Library'),
|
||||
'severity' => REQUIREMENT_OK,
|
||||
'value' => t('Installed'),
|
||||
];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the location of the Google Charts library.
|
||||
*
|
||||
* @return string
|
||||
* The location of the library, or FALSE if the library isn't installed.
|
||||
*/
|
||||
function charts_google_find_library() {
|
||||
// The following logic is taken from libraries_get_libraries()
|
||||
$searchdir = [];
|
||||
|
||||
// Similar to 'modules' and 'themes' directories inside an installation
|
||||
// profile, installation profiles may want to place libraries into a
|
||||
// 'libraries' directory.
|
||||
$searchdir[] = 'profiles/' . \Drupal::installProfile() . '/libraries';
|
||||
|
||||
// Always search libraries.
|
||||
$searchdir[] = 'libraries';
|
||||
|
||||
// Also search sites/<domain>/*.
|
||||
$container = \Drupal::getContainer();
|
||||
$site_path = $container->getParameter('site.path');
|
||||
$site_path = explode('/', $site_path);
|
||||
$site_name = $site_path[1];
|
||||
$searchdir[] = $site_name . '/libraries';
|
||||
|
||||
foreach ($searchdir as $dir) {
|
||||
if (file_exists($dir . '/google_charts/loader.js')) {
|
||||
return $dir . '/google_charts';
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
22
web/modules/charts/modules/charts_google/charts_google.libraries.yml
Executable file
22
web/modules/charts/modules/charts_google/charts_google.libraries.yml
Executable file
@@ -0,0 +1,22 @@
|
||||
charts_google:
|
||||
version: 1.x
|
||||
js:
|
||||
js/charts_google.js: { weight: -1 }
|
||||
dependencies:
|
||||
- core/drupal
|
||||
- core/drupalSettings
|
||||
- charts/global
|
||||
- core/once
|
||||
google:
|
||||
remote: 'https://www.gstatic.com/charts/loader.js'
|
||||
version: VERSION
|
||||
license:
|
||||
name: 'Apache 2.0'
|
||||
url: 'http://www.apache.org/licenses/LICENSE-2.0'
|
||||
gpl-compatible: false
|
||||
cdn:
|
||||
/libraries/google_charts/: https://www.gstatic.com/charts/
|
||||
js:
|
||||
/libraries/google_charts/loader.js: { }
|
||||
dependencies:
|
||||
- charts_google/charts_google
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Implement module related hooks for the charts_google module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_charts_version3_to_new_settings_structure_alter().
|
||||
*/
|
||||
function charts_google_charts_version3_to_new_settings_structure_alter(array &$new_settings, string $for) {
|
||||
$config_factory = \Drupal::configFactory();
|
||||
$google_config = $config_factory->getEditable('charts_google.settings');
|
||||
if ($google_config) {
|
||||
$google_config->delete();
|
||||
}
|
||||
}
|
||||
33
web/modules/charts/modules/charts_google/composer.json
Executable file
33
web/modules/charts/modules/charts_google/composer.json
Executable file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "drupal/charts_google",
|
||||
"description": "Charts module integration with Google Charts.",
|
||||
"type": "drupal-module",
|
||||
"homepage": "http://drupal.org/project/charts",
|
||||
"support": {
|
||||
"issues": "https://www.drupal.org/project/issues/charts"
|
||||
},
|
||||
"license": "GPL-2.0+",
|
||||
"require": {
|
||||
"google/charts": "45"
|
||||
},
|
||||
"repositories": {
|
||||
"google_charts": {
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "google/charts",
|
||||
"version": "45",
|
||||
"type": "drupal-library",
|
||||
"extra": {
|
||||
"installer-name": "google_charts"
|
||||
},
|
||||
"dist": {
|
||||
"url": "https://www.gstatic.com/charts/loader.js",
|
||||
"type": "file"
|
||||
},
|
||||
"require": {
|
||||
"composer/installers": "^1.0 || ^2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
charts.settings.library_plugin.google:
|
||||
type: charts_settings_library_plugin
|
||||
mapping:
|
||||
use_material_design:
|
||||
label: 'Use Material Design'
|
||||
type: boolean
|
||||
187
web/modules/charts/modules/charts_google/js/charts_google.js
Executable file
187
web/modules/charts/modules/charts_google/js/charts_google.js
Executable file
@@ -0,0 +1,187 @@
|
||||
/**
|
||||
* @file
|
||||
* JavaScript integration between Google and Drupal.
|
||||
*/
|
||||
(function (Drupal, drupalSettings, once) {
|
||||
|
||||
'use strict';
|
||||
|
||||
Drupal.googleCharts = Drupal.googleCharts || {charts: []};
|
||||
|
||||
/**
|
||||
* Behavior to initialize Google Charts.
|
||||
*
|
||||
* @type {{attach: Drupal.behaviors.chartsGooglecharts.attach}}
|
||||
*/
|
||||
Drupal.behaviors.chartsGooglecharts = {
|
||||
attach: function (context) {
|
||||
// Define a fallback value for globalOptions;
|
||||
const globalOptions = drupalSettings.charts === undefined ? {
|
||||
useMaterialDesign: false,
|
||||
chartType: 'bar'
|
||||
} : drupalSettings.charts.google.global_options;
|
||||
let useMaterialDesign = globalOptions.useMaterialDesign;
|
||||
let chartType = globalOptions.chartType;
|
||||
const materialDesignPackages = ['bar', 'line', 'spline', 'scatter', 'column',];
|
||||
const packages = ['corechart', 'gauge', 'table',];
|
||||
if (useMaterialDesign === 'true' && materialDesignPackages.indexOf(chartType) !== -1) {
|
||||
if (chartType === 'spline') {
|
||||
chartType = 'line';
|
||||
}
|
||||
if (chartType === 'column') {
|
||||
chartType = 'bar';
|
||||
}
|
||||
packages.push(chartType);
|
||||
}
|
||||
// Load Google Charts API.
|
||||
google.charts.load('current', {packages: packages});
|
||||
|
||||
// Re-draw charts if viewport size has been changed.
|
||||
window.addEventListener('resize', function () {
|
||||
Drupal.googleCharts.waitForFinalEvent(function () {
|
||||
// Re-draw Google Charts.
|
||||
Drupal.googleCharts.drawCharts();
|
||||
}, 200, 'reload-google-charts');
|
||||
});
|
||||
|
||||
// Draw Google Charts.
|
||||
Drupal.googleCharts.drawCharts();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to draw Google Charts.
|
||||
*/
|
||||
Drupal.googleCharts.drawCharts = function () {
|
||||
const contents = new Drupal.Charts.Contents();
|
||||
once('load-google-charts-item', '.charts-google').forEach(function (element) {
|
||||
if (element.dataset.hasOwnProperty('chart')) {
|
||||
const chartId = element.id;
|
||||
const dataAttributes = contents.getData(chartId);
|
||||
google.charts.setOnLoadCallback(Drupal.googleCharts.drawChart(chartId, dataAttributes['visualization'], dataAttributes['data'], dataAttributes['options']));
|
||||
if (element.nextElementSibling && element.nextElementSibling.hasAttribute('data-charts-debug-container')) {
|
||||
element.nextElementSibling.querySelector('code').innerText = JSON.stringify(dataAttributes, null, ' ');
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to draw a Google Chart.
|
||||
*
|
||||
* @param {string} chartId - Chart Id.
|
||||
* @param {string} chartType - Chart Type.
|
||||
* @param {string} dataTable - Data.
|
||||
* @param {string} googleChartOptions - Options.
|
||||
*
|
||||
* @return {function} Draw Chart.
|
||||
*/
|
||||
Drupal.googleCharts.drawChart = function (chartId, chartType, dataTable, googleChartOptions) {
|
||||
return function () {
|
||||
const data = google.visualization.arrayToDataTable(dataTable);
|
||||
const options = googleChartOptions;
|
||||
const googleChartTypeFormatted = chartType;
|
||||
|
||||
let visualizationNamespace = 'visualization';
|
||||
let visualizationClass = chartType;
|
||||
// Replace the 'Spline' chart type with 'Line'.
|
||||
if (visualizationClass === 'SplineChart') {
|
||||
visualizationClass = 'LineChart';
|
||||
}
|
||||
if (options.theme === 'material') {
|
||||
// Material Design wants to use the 'charts' namespace.
|
||||
visualizationNamespace = 'charts';
|
||||
// Strip the 'Chart' suffix from the chart type.
|
||||
visualizationClass = visualizationClass.replace('Chart', '');
|
||||
// Replace the 'Column' chart type with 'Bar'.
|
||||
if (visualizationClass === 'Column') {
|
||||
visualizationClass = 'Bar';
|
||||
}
|
||||
}
|
||||
console.log(visualizationClass);
|
||||
|
||||
let chart;
|
||||
switch (googleChartTypeFormatted) {
|
||||
case 'BarChart':
|
||||
case 'ColumnChart':
|
||||
case 'LineChart':
|
||||
case 'SplineChart':
|
||||
case 'ScatterChart':
|
||||
chart = new google[visualizationNamespace][visualizationClass](document.getElementById(chartId));
|
||||
break;
|
||||
|
||||
case 'DonutChart':
|
||||
case 'PieChart':
|
||||
chart = new google.visualization.PieChart(document.getElementById(chartId));
|
||||
break;
|
||||
|
||||
case 'BubbleChart':
|
||||
chart = new google.visualization.BubbleChart(document.getElementById(chartId));
|
||||
break;
|
||||
|
||||
case 'AreaChart':
|
||||
chart = new google.visualization.AreaChart(document.getElementById(chartId));
|
||||
break;
|
||||
|
||||
case 'Gauge':
|
||||
chart = new google.visualization.Gauge(document.getElementById(chartId));
|
||||
break;
|
||||
|
||||
case 'ComboChart':
|
||||
chart = new google.visualization.ComboChart(document.getElementById(chartId));
|
||||
break;
|
||||
|
||||
case 'GeoChart':
|
||||
chart = new google.visualization.GeoChart(document.getElementById(chartId));
|
||||
break;
|
||||
|
||||
case 'TableChart':
|
||||
chart = new google.visualization.Table(document.getElementById(chartId));
|
||||
}
|
||||
|
||||
let color_regex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
|
||||
if (options['colors'] && options['colors'].length > 10) {
|
||||
for (const i in options['colors']) {
|
||||
if (!color_regex.test(options['colors'][i])) {
|
||||
options['colors'][i] = '#FFFFFF';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rewrite the colorAxis item to include the colors: key
|
||||
if (typeof options['colorAxis'] != 'undefined') {
|
||||
const colors = options['colorAxis'];
|
||||
const num_colors = colors.length;
|
||||
options['colorAxis'] = colors.splice(num_colors);
|
||||
options['colorAxis'] = {colors: colors};
|
||||
}
|
||||
chart.draw(data, options);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to run a callback function once when triggering an event
|
||||
* multiple times.
|
||||
*
|
||||
* Example usage:
|
||||
* @code
|
||||
* window.addEventListener('resize', function () {
|
||||
* Drupal.googleCharts.waitForFinalEvent(function(){
|
||||
* alert('Resize...');
|
||||
* }, 500, "some unique string");
|
||||
* });
|
||||
* @endcode
|
||||
*/
|
||||
Drupal.googleCharts.waitForFinalEvent = (function () {
|
||||
let timers = {};
|
||||
return function (callback, ms, uniqueId) {
|
||||
if (!uniqueId) {
|
||||
uniqueId = "Don't call this twice without a uniqueId";
|
||||
}
|
||||
if (timers[uniqueId]) {
|
||||
clearTimeout(timers[uniqueId]);
|
||||
}
|
||||
timers[uniqueId] = setTimeout(callback, ms);
|
||||
};
|
||||
})();
|
||||
}(Drupal, drupalSettings, once));
|
||||
600
web/modules/charts/modules/charts_google/src/Plugin/chart/Library/Google.php
Executable file
600
web/modules/charts/modules/charts_google/src/Plugin/chart/Library/Google.php
Executable file
@@ -0,0 +1,600 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\charts_google\Plugin\chart\Library;
|
||||
|
||||
use Drupal\charts\Element\Chart as ChartElement;
|
||||
use Drupal\charts\Plugin\chart\Library\ChartBase;
|
||||
use Drupal\charts\TypeManager;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Render\ElementInfoManagerInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Define a concrete class for a Chart.
|
||||
*
|
||||
* @Chart(
|
||||
* id = "google",
|
||||
* name = @Translation("Google"),
|
||||
* types = {
|
||||
* "area",
|
||||
* "bar",
|
||||
* "bubble",
|
||||
* "column",
|
||||
* "donut",
|
||||
* "gauge",
|
||||
* "line",
|
||||
* "pie",
|
||||
* "scatter",
|
||||
* "spline",
|
||||
* },
|
||||
* )
|
||||
*/
|
||||
class Google extends ChartBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The element info manager.
|
||||
*
|
||||
* @var \Drupal\Core\Render\ElementInfoManagerInterface
|
||||
*/
|
||||
protected $elementInfo;
|
||||
|
||||
/**
|
||||
* The chart type manager.
|
||||
*
|
||||
* @var \Drupal\charts\TypeManager
|
||||
*/
|
||||
protected $chartTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\views\Plugin\Block\ViewsBlockBase object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The element info manager.
|
||||
* @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info
|
||||
* The element info manager.
|
||||
* @param \Drupal\charts\TypeManager $chart_type_manager
|
||||
* The chart type manager.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, ModuleHandlerInterface $module_handler, ElementInfoManagerInterface $element_info, TypeManager $chart_type_manager) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->elementInfo = $element_info;
|
||||
$this->chartTypeManager = $chart_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('module_handler'),
|
||||
$container->get('element_info'),
|
||||
$container->get('plugin.manager.charts_type')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
|
||||
return [
|
||||
'use_material_design' => FALSE,
|
||||
] + parent::defaultConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::buildConfigurationForm($form, $form_state);
|
||||
$form['placeholder'] = [
|
||||
'#title' => $this->t('Placeholder'),
|
||||
'#type' => 'fieldset',
|
||||
'#description' => $this->t(
|
||||
'This is a placeholder for Google-specific library options. If you would like to help build this out, please work from <a href="@issue_link">this issue</a>.', [
|
||||
'@issue_link' => Url::fromUri('https://www.drupal.org/project/charts/issues/3046980')
|
||||
->toString(),
|
||||
]),
|
||||
];
|
||||
$form['use_material_design'] = [
|
||||
'#title' => $this->t('Use Material Design'),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => $this->configuration['use_material_design'] ?? FALSE,
|
||||
'#description' => $this->t('Use Material Design for charts.'),
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit configurations.
|
||||
*
|
||||
* @param array $form
|
||||
* The form element.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state.
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::submitConfigurationForm($form, $form_state);
|
||||
|
||||
if (!$form_state->getErrors()) {
|
||||
$values = $form_state->getValue($form['#parents']);
|
||||
$this->configuration['use_material_design'] = $values['use_material_design'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preRender(array $element) {
|
||||
$chart_definition = [];
|
||||
// Convert the chart renderable to a proper definition.
|
||||
$chart_definition['visualization'] = $this->chartsGoogleVisualizationType($element['#chart_type']);
|
||||
$chart_definition = $this->chartsGooglePopulateChartOptions($element, $chart_definition);
|
||||
$chart_definition = $this->chartsGooglePopulateChartAxes($element, $chart_definition);
|
||||
$chart_definition = $this->chartsGooglePopulateChartData($element, $chart_definition);
|
||||
|
||||
if (!isset($element['#id'])) {
|
||||
$element['#id'] = Html::getUniqueId('google-chart-render');
|
||||
}
|
||||
|
||||
if (!empty($element['#height']) || !empty($element['#width'])) {
|
||||
$element['#attributes']['style'] = 'height:' . $element['#height'] . $element['#height_units'] . ';width:' . $element['#width'] . $element['#width_units'] . ';';
|
||||
}
|
||||
|
||||
// Trim out empty options.
|
||||
ChartElement::trimArray($chart_definition['options']);
|
||||
|
||||
$element['#attached']['library'][] = 'charts_google/google';
|
||||
$element['#attributes']['class'][] = 'charts-google';
|
||||
$element['#chart_definition'] = $chart_definition;
|
||||
|
||||
// Setting global options.
|
||||
$element['#attached']['drupalSettings']['charts']['google']['global_options'] = [
|
||||
'useMaterialDesign' => !empty($this->configuration['use_material_design']) ? 'true' : 'false',
|
||||
'chartType' => $element['#chart_type'],
|
||||
];
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to convert a Drupal renderable type to a Google visualization type.
|
||||
*/
|
||||
public function chartsGoogleVisualizationType($renderable_type) {
|
||||
$types = [
|
||||
'area' => 'AreaChart',
|
||||
'bar' => 'BarChart',
|
||||
'column' => 'ColumnChart',
|
||||
'line' => 'LineChart',
|
||||
'spline' => 'SplineChart',
|
||||
'pie' => 'PieChart',
|
||||
'donut' => 'DonutChart',
|
||||
'gauge' => 'Gauge',
|
||||
'scatter' => 'ScatterChart',
|
||||
'bubble' => 'BubbleChart',
|
||||
'geo' => 'GeoChart',
|
||||
'table' => 'TableChart',
|
||||
];
|
||||
$this->moduleHandler->alter('charts_google_visualization_types', $types);
|
||||
return $types[$renderable_type] ?? FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to populate main chart options.
|
||||
*
|
||||
* @param array $element
|
||||
* The element.
|
||||
* @param array $chart_definition
|
||||
* The chart definition.
|
||||
*
|
||||
* @return array
|
||||
* The returned chart definition.
|
||||
*/
|
||||
public function chartsGooglePopulateChartOptions(array $element, array $chart_definition) {
|
||||
if (!empty($this->configuration['use_material_design'])) {
|
||||
$chart_definition['options']['theme'] = 'material';
|
||||
$chart_definition['options']['chart']['title'] = $element['#title'] ?? NULL;
|
||||
$chart_definition['options']['chart']['subtitle'] = $element['#subtitle'] ?? NULL;
|
||||
if ($element['#chart_type'] === 'bar') {
|
||||
$chart_definition['options']['bars'] = 'horizontal';
|
||||
}
|
||||
}
|
||||
else {
|
||||
$title = $element['#title'] ?? NULL;
|
||||
if ($title && !empty($element['#subtitle'])) {
|
||||
$title .= ': ' . $element['#subtitle'];
|
||||
}
|
||||
$chart_definition['options']['title'] = $title;
|
||||
}
|
||||
$chart_definition['options']['titleTextStyle']['color'] = $element['#title_color'];
|
||||
$chart_definition['options']['titleTextStyle']['bold'] = $element['#title_font_weight'] === 'bold';
|
||||
$chart_definition['options']['titleTextStyle']['italic'] = $element['#title_font_style'] === 'italic';
|
||||
$chart_definition['options']['titleTextStyle']['fontSize'] = $element['#title_font_size'];
|
||||
$chart_definition['options']['titlePosition'] = $element['#title_position'];
|
||||
$chart_definition['options']['colors'] = $element['#colors'];
|
||||
$chart_definition['options']['fontName'] = $element['#font'];
|
||||
$chart_definition['options']['fontSize'] = $element['#font_size'];
|
||||
$chart_definition['options']['backgroundColor']['fill'] = $element['#background'];
|
||||
$chart_definition['options']['isStacked'] = (bool) $element['#stacking'];
|
||||
$chart_definition['options']['tooltip']['trigger'] = $element['#tooltips'] ? 'focus' : 'none';
|
||||
$chart_definition['options']['tooltip']['isHtml'] = (bool) $element['#tooltips_use_html'];
|
||||
$chart_definition['options']['pieSliceText'] = $element['#data_labels'] ? NULL : 'none';
|
||||
$chart_definition['options']['legend']['position'] = $element['#legend_position'] ?? 'none';
|
||||
$chart_definition['options']['legend']['alignment'] = 'center';
|
||||
$chart_definition['options']['interpolateNulls'] = TRUE;
|
||||
if ($element['#chart_type'] === 'gauge') {
|
||||
$chart_definition['options']['redFrom'] = $element['#gauge']['red_from'];
|
||||
$chart_definition['options']['redTo'] = $element['#gauge']['red_to'];
|
||||
$chart_definition['options']['yellowFrom'] = $element['#gauge']['yellow_from'];
|
||||
$chart_definition['options']['yellowTo'] = $element['#gauge']['yellow_to'];
|
||||
$chart_definition['options']['GreenFrom'] = $element['#gauge']['green_from'];
|
||||
$chart_definition['options']['greenTo'] = $element['#gauge']['green_to'];
|
||||
$chart_definition['options']['min'] = (int) $element['#gauge']['min'];
|
||||
$chart_definition['options']['max'] = (int) $element['#gauge']['max'];
|
||||
}
|
||||
if ($element['#chart_type'] === 'donut') {
|
||||
$chart_definition['options']['pieHole'] = 0.4;
|
||||
}
|
||||
if ($element['#chart_type'] === 'bubble') {
|
||||
$chart_definition['options']['bubble']['textStyle']['fontSize'] = 11;
|
||||
}
|
||||
// @todo Legend title (and thus these properties) not supported by Google.
|
||||
$chart_definition['options']['legend']['title'] = $element['#legend_title'];
|
||||
$chart_definition['options']['legend']['titleTextStyle']['bold'] = $element['#legend_title_font_weight'] === 'bold';
|
||||
$chart_definition['options']['legend']['titleTextStyle']['italic'] = $element['#legend_title_font_style'] === 'italic';
|
||||
$chart_definition['options']['legend']['titleTextStyle']['fontSize'] = $element['#legend_title_font_size'];
|
||||
|
||||
$chart_definition['options']['legend']['textStyle']['bold'] = $element['#legend_font_weight'] === 'bold';
|
||||
$chart_definition['options']['legend']['textStyle']['italic'] = $element['#legend_font_style'] === 'italic';
|
||||
$chart_definition['options']['legend']['textStyle']['fontSize'] = $element['#legend_font_size'];
|
||||
// If your labels are truncated, you may need to add (try adjusting the %).
|
||||
// $chart_definition['options']['chartArea']['height'] = '50%'.
|
||||
$chart_definition['options']['animation']['duration'] = 10000;
|
||||
$chart_definition['options']['animation']['easing'] = 'out';
|
||||
|
||||
// Merge in chart raw options.
|
||||
if (!empty($element['#raw_options'])) {
|
||||
$chart_definition = NestedArray::mergeDeepArray([
|
||||
$chart_definition,
|
||||
$element['#raw_options'],
|
||||
]);
|
||||
}
|
||||
|
||||
return $chart_definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to populate chart axes.
|
||||
*
|
||||
* @param array $element
|
||||
* The element.
|
||||
* @param array $chart_definition
|
||||
* The chart definition.
|
||||
*
|
||||
* @return array
|
||||
* Return the chart definition.
|
||||
*/
|
||||
public function chartsGooglePopulateChartAxes(array $element, array $chart_definition) {
|
||||
foreach (Element::children($element) as $key) {
|
||||
if ($element[$key]['#type'] === 'chart_xaxis' || $element[$key]['#type'] === 'chart_yaxis') {
|
||||
// Make sure defaults are loaded.
|
||||
if (empty($element[$key]['#defaults_loaded'])) {
|
||||
$element[$key] += $this->elementInfo->getInfo($element[$key]['#type']);
|
||||
}
|
||||
|
||||
// Populate the chart data.
|
||||
$axis = [];
|
||||
$axis['title'] = $element[$key]['#title'] ?? '';
|
||||
$axis['titleTextStyle']['color'] = $element[$key]['#title_color'];
|
||||
$axis['titleTextStyle']['bold'] = $element[$key]['#title_font_weight'] === 'bold';
|
||||
$axis['titleTextStyle']['italic'] = $element[$key]['#title_font_style'] === 'italic';
|
||||
$axis['titleTextStyle']['fontSize'] = $element[$key]['#title_font_size'];
|
||||
// In Google, the row column of data is used as labels.
|
||||
if ($element[$key]['#labels'] && $element[$key]['#type'] === 'chart_xaxis') {
|
||||
foreach ($element[$key]['#labels'] as $label_key => $label) {
|
||||
$chart_definition['data'][$label_key + 1][0] = $label;
|
||||
}
|
||||
}
|
||||
$axis['textStyle']['color'] = $element[$key]['#labels_color'];
|
||||
$axis['textStyle']['bold'] = $element[$key]['#labels_font_weight'] === 'bold';
|
||||
$axis['textStyle']['italic'] = $element[$key]['#labels_font_style'] === 'italic';
|
||||
$axis['textStyle']['fontSize'] = $element[$key]['#labels_font_size'];
|
||||
$axis['slantedText'] = !empty($element[$key]['#labels_rotation']) ? TRUE : NULL;
|
||||
$axis['slantedTextAngle'] = $element[$key]['#labels_rotation'];
|
||||
$axis['gridlines']['color'] = $element[$key]['#grid_line_color'];
|
||||
$axis['baselineColor'] = $element[$key]['#base_line_color'];
|
||||
$axis['minorGridlines']['color'] = $element[$key]['#minor_grid_line_color'];
|
||||
$axis['viewWindowMode'] = isset($element[$key]['#max']) ? 'explicit' : NULL;
|
||||
$axis['viewWindow']['max'] = !empty($element[$key]['#max']) ? (int) $element[$key]['#max'] : NULL;
|
||||
$axis['viewWindow']['min'] = !empty($element[$key]['#min']) ? (int) $element[$key]['#min'] : NULL;
|
||||
|
||||
// Merge in axis raw options.
|
||||
if (!empty($element[$key]['#raw_options'])) {
|
||||
$axis = NestedArray::mergeDeepArray([
|
||||
$axis,
|
||||
$element[$key]['#raw_options'],
|
||||
]);
|
||||
}
|
||||
|
||||
// Multi-axis support only applies to the major axis in Google charts.
|
||||
$chart_type = $this->chartTypeManager->getDefinition($element['#chart_type']);
|
||||
$axis_index = $element[$key]['#opposite'] ? 1 : 0;
|
||||
if ($element[$key]['#type'] === 'chart_xaxis') {
|
||||
$axis_keys = !$chart_type['axis_inverted'] ? ['hAxis'] : [
|
||||
'vAxes',
|
||||
$axis_index,
|
||||
];
|
||||
}
|
||||
else {
|
||||
$axis_keys = !$chart_type['axis_inverted'] ? ['vAxes', $axis_index] : ['hAxis'];
|
||||
}
|
||||
$axis_drilldown = &$chart_definition['options'];
|
||||
foreach ($axis_keys as $axis_key) {
|
||||
$axis_drilldown = &$axis_drilldown[$axis_key];
|
||||
}
|
||||
$axis_drilldown = $axis;
|
||||
}
|
||||
}
|
||||
|
||||
return $chart_definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to populate chart data.
|
||||
*
|
||||
* @param array $element
|
||||
* The element.
|
||||
* @param array $chart_definition
|
||||
* The chart definition.
|
||||
*
|
||||
* @return array
|
||||
* Return the chart definition.
|
||||
*/
|
||||
public function chartsGooglePopulateChartData(array &$element, array $chart_definition) {
|
||||
$chart_definition['options']['series'] = [];
|
||||
$chart_type = $this->chartTypeManager->getDefinition($element['#chart_type']);
|
||||
$series_number = 0;
|
||||
foreach (Element::children($element) as $key) {
|
||||
if ($element[$key]['#type'] === 'chart_data') {
|
||||
$series = [];
|
||||
|
||||
// Make sure defaults are loaded.
|
||||
if (empty($element[$key]['#defaults_loaded'])) {
|
||||
$element[$key] += $this->elementInfo->getInfo($element[$key]['#type']);
|
||||
}
|
||||
|
||||
// Convert target named axis keys to integers.
|
||||
$axis_index = 0;
|
||||
if (isset($element[$key]['#target_axis'])) {
|
||||
$axis_name = $element[$key]['#target_axis'];
|
||||
foreach (Element::children($element) as $axis_key) {
|
||||
$multi_axis_type = $chart_type['axis_inverted'] ? 'chart_xaxis' : 'chart_yaxis';
|
||||
if ($element[$axis_key]['#type'] === $multi_axis_type) {
|
||||
if ($axis_key === $axis_name) {
|
||||
break;
|
||||
}
|
||||
$axis_index++;
|
||||
}
|
||||
}
|
||||
$series['targetAxisIndex'] = $axis_index;
|
||||
}
|
||||
|
||||
// Allow data to provide the labels. This will override the axis
|
||||
// settings.
|
||||
if ($element[$key]['#labels']) {
|
||||
foreach ($element[$key]['#labels'] as $label_index => $label) {
|
||||
$chart_definition['data'][$label_index + 1][0] = $label;
|
||||
}
|
||||
}
|
||||
if ($element[$key]['#title']) {
|
||||
$chart_definition['data'][0][$series_number + 1] = $element[$key]['#title'];
|
||||
}
|
||||
foreach ($element[$key]['#data'] as $index => $data_value) {
|
||||
// Nested array values typically used for scatter charts. This weird
|
||||
// approach leaves columns empty in order to make arbitrary pairings.
|
||||
// See https://developers.google.com/chart/interactive/docs/gallery/scatterchart#Data_Format
|
||||
if (is_array($data_value)) {
|
||||
if ($chart_type['id'] === 'scatter') {
|
||||
$chart_definition['data'][] = [
|
||||
0 => $data_value[0],
|
||||
$series_number + 1 => $data_value[1],
|
||||
];
|
||||
}
|
||||
elseif ($chart_type['id'] === 'bubble') {
|
||||
$chart_definition['data'][] = [
|
||||
0 => $element[$key]['#title'],
|
||||
1 => $data_value[0],
|
||||
$series_number + 2 => $data_value[1],
|
||||
$series_number + 3 => $data_value[2],
|
||||
];
|
||||
}
|
||||
else {
|
||||
if (!empty($data_value['color']) && (in_array($chart_type['id'], [
|
||||
'pie',
|
||||
'donut',
|
||||
]))) {
|
||||
$chart_definition['options']['slices'][$index]['color'] = $data_value['color'];
|
||||
unset($data_value['color']);
|
||||
$data_value = array_values($data_value);
|
||||
}
|
||||
$chart_definition['data'][$index + 1] = $data_value;
|
||||
}
|
||||
}
|
||||
// Most charts provide a single-dimension array of values.
|
||||
else {
|
||||
if ($chart_type['id'] === 'gauge') {
|
||||
$data_value = [$element[$key]['#title'], $data_value];
|
||||
$chart_definition['data'][$index + 1] = $data_value;
|
||||
}
|
||||
else {
|
||||
$chart_definition['data'][$index + 1][$series_number + 1] = $data_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$series['color'] = $element[$key]['#color'];
|
||||
// Scatter charts are not visible if the data_markers are disabled.
|
||||
if ($chart_type['id'] === 'scatter') {
|
||||
$element['#data_markers'] = TRUE;
|
||||
}
|
||||
$series['pointSize'] = !empty($element['#data_markers']) ? 3 : 0;
|
||||
$series['visibleInLegend'] = $element[$key]['#show_in_legend'];
|
||||
|
||||
// Labels only supported on pies.
|
||||
$series['pieSliceText'] = $element[$key]['#show_labels'] ? 'label' : 'none';
|
||||
|
||||
// These properties are not real Google Charts properties. They are
|
||||
// utilized by the formatter in charts_google.js.
|
||||
$decimal_count = $element[$key]['#decimal_count'] ? '.' . str_repeat('0', $element[$key]['#decimal_count']) : '';
|
||||
$prefix = $this->chartsGoogleEscapeIcuCharacters($element[$key]['#prefix'] ?? '');
|
||||
$suffix = $this->chartsGoogleEscapeIcuCharacters($element[$key]['#suffix'] ?? '');
|
||||
$format = $prefix . '#' . $decimal_count . $suffix;
|
||||
$series['_format']['format'] = $format;
|
||||
|
||||
// @todo Convert this from PHP's date format to ICU format.
|
||||
// See https://developers.google.com/chart/interactive/docs/reference#dateformatter.
|
||||
// $series['_format']['dateFormat'] = $element[$key]['#date_format'];
|
||||
// Conveniently only the axis that supports multiple axes is the one;
|
||||
// that can receive formatting, so we know that the key will;
|
||||
// always be plural.
|
||||
$axis_type = $chart_type['axis_inverted'] ? 'hAxes' : 'vAxes';
|
||||
$chart_definition['options'][$axis_type][$axis_index]['format'] = $format;
|
||||
|
||||
// Convert to a ComboChart if mixing types.
|
||||
// See https://developers.google.com/chart/interactive/docs/gallery/combochart?hl=en.
|
||||
if ($element[$key]['#chart_type'] && !in_array($element[$key]['#chart_type'], ['bubble', 'scatter'])) {
|
||||
// Oddly Google calls a "column" chart a "bars" series.
|
||||
// Using actual bar.
|
||||
// charts is not supported in combo charts with Google.
|
||||
$main_chart_type = $element['#chart_type'] === 'column' ? 'bars' : $element['#chart_type'];
|
||||
$chart_definition['visualization'] = 'ComboChart';
|
||||
$chart_definition['options']['seriesType'] = $main_chart_type;
|
||||
|
||||
$data_chart_type = $element[$key]['#chart_type'] === 'column' ? 'bars' : $element[$key]['#chart_type'];
|
||||
$series['type'] = $data_chart_type;
|
||||
}
|
||||
|
||||
// Merge in point raw options.
|
||||
if (!empty($data_item['#raw_options'])) {
|
||||
$series = NestedArray::mergeDeepArray([
|
||||
$series,
|
||||
$data_item['#raw_options'],
|
||||
]);
|
||||
}
|
||||
|
||||
// Add the series to the main chart definition.
|
||||
ChartElement::trimArray($series);
|
||||
$chart_definition['options']['series'][$series_number] = $series;
|
||||
|
||||
// Merge in any point-specific data points.
|
||||
foreach (Element::children($element[$key]) as $sub_key) {
|
||||
if ($element[$key][$sub_key]['#type'] === 'chart_data_item') {
|
||||
|
||||
// Make sure defaults are loaded.
|
||||
if (empty($element[$key][$sub_key]['#defaults_loaded'])) {
|
||||
$element[$key][$sub_key] += $this->elementInfo->getInfo($element[$key][$sub_key]['#type']);
|
||||
}
|
||||
|
||||
$data_item = $element[$key][$sub_key];
|
||||
if ($data_item['#data']) {
|
||||
$chart_definition['data'][$sub_key + 1][$series_number + 1] = $data_item['#data'];
|
||||
}
|
||||
// These data properties are manually applied to cells in JS.
|
||||
// Color role not yet supported. See https://code.google.com/p/google-visualization-api-issues/issues/detail?id=1267
|
||||
$chart_definition['_data'][$sub_key + 1][$series_number + 1]['color'] = $data_item['#color'];
|
||||
$chart_definition['_data'][$sub_key + 1][$series_number + 1]['tooltip'] = $data_item['#title'];
|
||||
|
||||
// Merge in data point raw options.
|
||||
if (!empty($data_item['#raw_options'])) {
|
||||
$chart_definition['_data'][$sub_key + 1][$series_number + 1] = NestedArray::mergeDeepArray([
|
||||
$chart_definition['_data'][$sub_key + 1][$series_number + 1],
|
||||
$data_item['#raw_options'],
|
||||
]);
|
||||
}
|
||||
|
||||
ChartElement::trimArray($chart_definition['_data'][$sub_key + 1][$series_number + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
$series_number++;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Once complete, normalize the chart data to ensure a full 2D structure.
|
||||
$data = $chart_definition['data'];
|
||||
|
||||
if (in_array($element['#chart_type'], ['pie', 'donut', 'scatter'])) {
|
||||
// Populate the 0th row with the same number of values as the 1st row.
|
||||
$data[0] = array_fill(0, count($data[1]), $element[$key]['#title'] ?? '');
|
||||
}
|
||||
elseif ($element['#chart_type'] === 'bubble') {
|
||||
$data[0] = [
|
||||
$element[$key]['#title'] ?? '',
|
||||
$chart_definition['options']['hAxis']['title'] ?? '',
|
||||
$chart_definition['options']['vAxes'][0]['title'] ?? '',
|
||||
$chart_definition['options']['zAxis']['title'] ?? '',
|
||||
];
|
||||
}
|
||||
else {
|
||||
array_unshift($data[0], '');
|
||||
}
|
||||
// Ensure consistent column count.
|
||||
$column_count = count($data[0]);
|
||||
$new_data = [];
|
||||
foreach ($data as $row => $values) {
|
||||
for ($n = 0; $n < $column_count; $n++) {
|
||||
if (!empty(array_values($values))) {
|
||||
$temp = array_values($values);
|
||||
$new_data[$row][$n] = $temp[$n] ?? NULL;
|
||||
}
|
||||
}
|
||||
ksort($new_data[$row]);
|
||||
}
|
||||
ksort($new_data);
|
||||
|
||||
$chart_definition['data'] = $new_data;
|
||||
|
||||
return $chart_definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to escape special characters in ICU number formats.
|
||||
*
|
||||
* Google will use the ICU format to auto-adjust numbers based on special
|
||||
* characters that are used in the format. This function escapes these special
|
||||
* characters so they just show up as the character specified.
|
||||
*
|
||||
* The format string is a subset of the ICU pattern set. For instance,
|
||||
* {pattern:'#,###%'} will result in output values "1,000%", "750%", and "50%"
|
||||
* for values 10, 7.5, and 0.5.
|
||||
*/
|
||||
public function chartsGoogleEscapeIcuCharacters($string) {
|
||||
return preg_replace('/([0-9@#\.\-,E\+;%\'\*])/', "'$1'", $string);
|
||||
}
|
||||
|
||||
}
|
||||
130
web/modules/charts/modules/charts_highcharts/README.md
Executable file
130
web/modules/charts/modules/charts_highcharts/README.md
Executable file
@@ -0,0 +1,130 @@
|
||||
#Installation using Composer (recommended)
|
||||
|
||||
If you use Composer to manage dependencies, edit your site's "composer.json"
|
||||
file as follows.
|
||||
|
||||
1. Run `composer require --prefer-dist composer/installers` to ensure that
|
||||
you have the "composer/installers" package installed. This package
|
||||
facilitates the installation of packages into directories other than
|
||||
"/vendor" (e.g. "/libraries") using Composer.
|
||||
|
||||
2. Add the following to the "installer-paths" section of "composer.json":
|
||||
|
||||
"libraries/{$name}": ["type:drupal-library"],
|
||||
|
||||
3. Add the following to the "repositories" section of "composer.json":
|
||||
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "highcharts/highcharts",
|
||||
"version": "11.1.0",
|
||||
"type": "drupal-library",
|
||||
"extra": {
|
||||
"installer-name": "highcharts"
|
||||
},
|
||||
"dist": {
|
||||
"url": "https://code.highcharts.com/11.1.0/highcharts.js",
|
||||
"type": "file"
|
||||
},
|
||||
"require": {
|
||||
"composer/installers": "^1.0 || ^2.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "highcharts/more",
|
||||
"version": "11.1.0",
|
||||
"type": "drupal-library",
|
||||
"extra": {
|
||||
"installer-name": "highcharts_more"
|
||||
},
|
||||
"dist": {
|
||||
"url": "https://code.highcharts.com/11.1.0/highcharts-more.js",
|
||||
"type": "file"
|
||||
},
|
||||
"require": {
|
||||
"composer/installers": "^1.0 || ^2.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "highcharts/exporting",
|
||||
"version": "11.1.0",
|
||||
"type": "drupal-library",
|
||||
"extra": {
|
||||
"installer-name": "highcharts_exporting"
|
||||
},
|
||||
"dist": {
|
||||
"url": "https://code.highcharts.com/11.1.0/modules/exporting.js",
|
||||
"type": "file"
|
||||
},
|
||||
"require": {
|
||||
"composer/installers": "^1.0 || ^2.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "highcharts/export-data",
|
||||
"version": "11.1.0",
|
||||
"type": "drupal-library",
|
||||
"extra": {
|
||||
"installer-name": "highcharts_export-data"
|
||||
},
|
||||
"dist": {
|
||||
"url": "https://code.highcharts.com/11.1.0/modules/export-data.js",
|
||||
"type": "file"
|
||||
},
|
||||
"require": {
|
||||
"composer/installers": "^1.0 || ^2.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "highcharts/accessibility",
|
||||
"version": "11.1.0",
|
||||
"type": "drupal-library",
|
||||
"extra": {
|
||||
"installer-name": "highcharts_accessibility"
|
||||
},
|
||||
"dist": {
|
||||
"url": "https://code.highcharts.com/11.1.0/modules/accessibility.js",
|
||||
"type": "file"
|
||||
},
|
||||
"require": {
|
||||
"composer/installers": "^1.0 || ^2.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "highcharts/3d",
|
||||
"version": "11.1.0",
|
||||
"type": "drupal-library",
|
||||
"extra": {
|
||||
"installer-name": "highcharts_3d"
|
||||
},
|
||||
"dist": {
|
||||
"url": "https://code.highcharts.com/11.1.0/highcharts-3d.js",
|
||||
"type": "file"
|
||||
},
|
||||
"require": {
|
||||
"composer/installers": "^1.0 || ^2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4. Run `composer require --prefer-dist highcharts/highcharts:11.1.0
|
||||
highcharts/more:11.1.0 highcharts/exporting:11.1.0
|
||||
highcharts/export-data:11.1.0 highcharts/accessibility:11.1.0
|
||||
highcharts/3d:11.1.0` - you should find that new directories have been
|
||||
created under "/libraries"
|
||||
@@ -0,0 +1,12 @@
|
||||
name: Highcharts
|
||||
type: module
|
||||
description: 'Charts module integration with Highcharts library.'
|
||||
package: Charts
|
||||
core_version_requirement: ^10.3 || ^11
|
||||
dependencies:
|
||||
- charts:charts
|
||||
|
||||
# Information added by Drupal.org packaging script on 2024-12-02
|
||||
version: '5.1.1'
|
||||
project: 'charts'
|
||||
datestamp: 1733170633
|
||||
95
web/modules/charts/modules/charts_highcharts/charts_highcharts.install
Executable file
95
web/modules/charts/modules/charts_highcharts/charts_highcharts.install
Executable file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Installation and update hooks for the Charts Highcharts module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_requirements().
|
||||
*/
|
||||
function charts_highcharts_requirements($phase) {
|
||||
$requirements = [];
|
||||
|
||||
switch ($phase) {
|
||||
case 'runtime':
|
||||
$library_path = charts_highcharts_find_library();
|
||||
$config = \Drupal::config('charts.settings');
|
||||
$cdn = $config->get('advanced.requirements.cdn') ?? FALSE;
|
||||
|
||||
if (!$library_path) {
|
||||
if ($cdn) {
|
||||
$requirements['charts_highcharts_js'] = [
|
||||
'title' => t('Highcharts Library'),
|
||||
'value' => t('Available through a CDN'),
|
||||
'severity' => REQUIREMENT_WARNING,
|
||||
'description' => t('You are using the Highcharts library via a content delivery network. It is generally considered better practice to install the library files locally. Please see the README file inside charts_highcharts for instructions to install the library.'),
|
||||
];
|
||||
}
|
||||
else {
|
||||
$requirements['charts_highcharts_js'] = [
|
||||
'title' => t('Highcharts Library'),
|
||||
'value' => t('Not Installed'),
|
||||
'severity' => REQUIREMENT_ERROR,
|
||||
'description' => t('You are missing the Highcharts library in your Drupal installation directory and you have opted not to use a CDN. Please either enable use of the CDN in the Chart Settings under the Advanced tab or see the README file inside charts_highcharts for instructions to install the library.'),
|
||||
];
|
||||
}
|
||||
}
|
||||
else {
|
||||
$requirements['charts_highcharts_js'] = [
|
||||
'title' => t('Highcharts Library'),
|
||||
'value' => t('Installed'),
|
||||
'severity' => REQUIREMENT_OK,
|
||||
'description' => t('You have installed the Highcharts.js library in your Drupal installation directory.'),
|
||||
];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (function_exists('libraries_detect') && $highcharts_info = libraries_detect('highcharts')) {
|
||||
if (is_dir($highcharts_info['library path'] . '/js/exporting-server')) {
|
||||
$requirements['charts_highcharts_js'] = [
|
||||
'title' => t('Highcharts vulnerability'),
|
||||
'severity' => REQUIREMENT_ERROR,
|
||||
'value' => t('Dangerous sample code present'),
|
||||
'description' => t('Your installation of the Highcharts library at "@path" contains a directory named "exporting-server". This directory contains dangerous sample files that may compromise the security of your site. You must delete this directory before you may use the Charts Highcharts module.', ['@path' => $highcharts_info['library path']]),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the location of the Highcharts library.
|
||||
*
|
||||
* @return string
|
||||
* The location of the library, or FALSE if the library isn't installed.
|
||||
*/
|
||||
function charts_highcharts_find_library() {
|
||||
// The following logic is taken from libraries_get_libraries()
|
||||
$searchdir = [];
|
||||
|
||||
// Similar to 'modules' and 'themes' directories inside an installation
|
||||
// profile, installation profiles may want to place libraries into a
|
||||
// 'libraries' directory.
|
||||
$searchdir[] = 'profiles/' . \Drupal::installProfile() . '/libraries';
|
||||
|
||||
// Always search libraries.
|
||||
$searchdir[] = 'libraries';
|
||||
|
||||
// Also search sites/<domain>/*.
|
||||
$container = \Drupal::getContainer();
|
||||
$site_path = $container->getParameter('site.path');
|
||||
$site_path = explode('/', $site_path);
|
||||
$site_name = $site_path[1];
|
||||
$searchdir[] = $site_name . '/libraries';
|
||||
|
||||
foreach ($searchdir as $dir) {
|
||||
if (file_exists($dir . '/highcharts/highcharts.js')) {
|
||||
return $dir . '/highcharts';
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
73
web/modules/charts/modules/charts_highcharts/charts_highcharts.libraries.yml
Executable file
73
web/modules/charts/modules/charts_highcharts/charts_highcharts.libraries.yml
Executable file
@@ -0,0 +1,73 @@
|
||||
charts_highcharts:
|
||||
version: VERSION
|
||||
js:
|
||||
js/charts_highcharts.js: { weight: -1 }
|
||||
dependencies:
|
||||
- core/once
|
||||
- core/drupalSettings
|
||||
- charts/global
|
||||
|
||||
highcharts:
|
||||
remote: https://code.highcharts.com/highcharts.js
|
||||
version: VERSION
|
||||
license:
|
||||
name: Non-commercial
|
||||
url: https://creativecommons.org/licenses/by-nc/3.0/
|
||||
gpl-compatible: false
|
||||
cdn:
|
||||
/libraries/highcharts/highcharts.js: https://code.highcharts.com/11.1.0/highcharts.js
|
||||
/libraries/highcharts_more/highcharts-more.js: https://code.highcharts.com/11.1.0/highcharts-more.js
|
||||
/libraries/highcharts_accessibility/accessibility.js: https://code.highcharts.com/11.1.0/modules/accessibility.js
|
||||
/libraries/highcharts_3d/highcharts-3d.js: https://code.highcharts.com/11.1.0/highcharts-3d.js
|
||||
js:
|
||||
/libraries/highcharts/highcharts.js: { minified: true }
|
||||
/libraries/highcharts_more/highcharts-more.js: { minified: true }
|
||||
/libraries/highcharts_accessibility/accessibility.js: { minified: true }
|
||||
/libraries/highcharts_3d/highcharts-3d.js: { minified: true }
|
||||
dependencies:
|
||||
- charts_highcharts/charts_highcharts
|
||||
|
||||
highcharts_exporting:
|
||||
remote: https://code.highcharts.com/highcharts.js
|
||||
version: VERSION
|
||||
license:
|
||||
name: Non-commercial
|
||||
url: https://creativecommons.org/licenses/by-nc/3.0/
|
||||
gpl-compatible: false
|
||||
cdn:
|
||||
/libraries/highcharts_exporting/exporting.js: https://code.highcharts.com/11.1.0/modules/exporting.js
|
||||
/libraries/highcharts_export-data/export-data.js: https://code.highcharts.com/11.1.0/modules/export-data.js
|
||||
js:
|
||||
/libraries/highcharts_exporting/exporting.js: { minified: true }
|
||||
/libraries/highcharts_export-data/export-data.js: { minified: true }
|
||||
dependencies:
|
||||
- charts_highcharts/charts_highcharts
|
||||
- charts_highcharts/highcharts
|
||||
|
||||
texture:
|
||||
remote: https://code.highcharts.com/highcharts.js
|
||||
version: VERSION
|
||||
license:
|
||||
name: Non-commercial
|
||||
url: https://creativecommons.org/licenses/by-nc/3.0/
|
||||
gpl-compatible: false
|
||||
cdn:
|
||||
/libraries/highcharts_pattern-fill/pattern-fill.js: https://code.highcharts.com/11.1.0/modules/pattern-fill.js
|
||||
/libraries/highcharts_high-contrast-light/high-contrast-light.js: https://code.highcharts.com/11.1.0/themes/high-contrast-light.js
|
||||
js:
|
||||
/libraries/highcharts_pattern-fill/pattern-fill.js: { minified: true }
|
||||
/libraries/highcharts_high-contrast-light/high-contrast-light.js: { minified: true }
|
||||
js/textures.js: { weight: -5 }
|
||||
dependencies:
|
||||
- charts/global
|
||||
- core/once
|
||||
- charts_highcharts/charts_highcharts
|
||||
|
||||
color_changer:
|
||||
version: VERSION
|
||||
js:
|
||||
js/color_changer.js: { }
|
||||
dependencies:
|
||||
- charts/global
|
||||
- core/once
|
||||
- charts_highcharts/charts_highcharts
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Implement module related hooks for the charts_highcharts module.
|
||||
*/
|
||||
|
||||
use Drupal\charts\ConfigUpdater;
|
||||
use Drupal\charts_highcharts\Plugin\chart\Library\Highcharts;
|
||||
|
||||
/**
|
||||
* Implements hook_charts_version3_to_new_settings_structure_alter().
|
||||
*/
|
||||
function charts_highcharts_charts_version3_to_new_settings_structure_alter(array &$new_settings, string $for, ConfigUpdater $config_updater) {
|
||||
$config_factory = \Drupal::configFactory();
|
||||
$is_config = $for === 'config';
|
||||
$highcharts_config = $is_config ? $config_factory->getEditable('charts_highcharts.settings') : NULL;
|
||||
if (!$is_config || !$highcharts_config || empty($new_settings['library']) || $new_settings['library'] !== 'highcharts') {
|
||||
if ($highcharts_config) {
|
||||
$highcharts_config->delete();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$mapping_keys = [
|
||||
'legend_layout' => 'legend_layout',
|
||||
'legend_background_color' => 'legend_background_color',
|
||||
'legend_border_width' => 'legend_border_width',
|
||||
'legend_shadow' => 'legend_shadow',
|
||||
'item_style_color' => 'legend_item_style_color',
|
||||
'text_overflow' => 'legend_item_style_overflow',
|
||||
];
|
||||
$library_config = [
|
||||
'legend' => [
|
||||
'layout' => NULL,
|
||||
'background_color' => '',
|
||||
'border_width' => 0,
|
||||
'shadow' => FALSE,
|
||||
'item_style' => [
|
||||
'color' => '',
|
||||
'overflow' => '',
|
||||
],
|
||||
],
|
||||
'exporting_library' => TRUE,
|
||||
'texture_library' => TRUE,
|
||||
'global_options' => Highcharts::defaultGlobalOptions(),
|
||||
];
|
||||
|
||||
foreach ($highcharts_config->getRawData() as $config_key => $config_value) {
|
||||
$setting_key_map = $mapping_keys[$config_key] ?? '';
|
||||
if (!$setting_key_map || substr($setting_key_map, 0, 6) !== 'legend') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Stripping the 'legend_' in front of the mapping key.
|
||||
$setting_key_map = substr($setting_key_map, 7, strlen($setting_key_map));
|
||||
$value = $config_updater->transformBoolStringValueToBool($config_value);
|
||||
switch ($setting_key_map) {
|
||||
case 'layout':
|
||||
$library_config['legend']['layout'] = $value;
|
||||
break;
|
||||
|
||||
case 'background_color':
|
||||
$library_config['legend']['background_color'] = $value;
|
||||
break;
|
||||
|
||||
case 'border_width':
|
||||
$library_config['legend']['border_width'] = $value;
|
||||
break;
|
||||
|
||||
case 'shadow':
|
||||
$library_config['legend']['shadow'] = $value;
|
||||
break;
|
||||
|
||||
case 'item_style_color':
|
||||
$library_config['legend']['item_style']['color'] = $value;
|
||||
break;
|
||||
|
||||
default:
|
||||
$library_config['legend']['item_style']['overflow'] = $value;
|
||||
}
|
||||
|
||||
$new_settings['library_config'] = $library_config;
|
||||
$highcharts_config->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Charts highcharts post-update file.
|
||||
*/
|
||||
|
||||
use Drupal\charts_highcharts\Plugin\chart\Library\Highcharts;
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Charts highcharts post-update file.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Set default global options.
|
||||
*/
|
||||
function charts_highcharts_post_update_set_default_global_options(&$sandbox) {
|
||||
/** @var \Drupal\Core\Config\Config $config */
|
||||
$config = \Drupal::service('config.factory')->getEditable('charts.settings');
|
||||
$library_plugin_id = $config->get('charts_default_settings.library');
|
||||
$library_is_highcharts = $library_plugin_id === 'highcharts';
|
||||
$global_options_config_key = 'charts_default_settings.library_config.global_options';
|
||||
// Getting the current config key to ensure that we don't overwrite the
|
||||
// existing configurations in case the user saved the config before running
|
||||
// this update.
|
||||
$global_options = $library_is_highcharts ? $config->get($global_options_config_key) : [];
|
||||
if ($library_is_highcharts && !$global_options) {
|
||||
$config->set($global_options_config_key, Highcharts::defaultGlobalOptions());
|
||||
return 'Global options config were updated.';
|
||||
}
|
||||
return 'Global options update were not set because the default library is not highcharts or existing global options were saved before running post update.';
|
||||
}
|
||||
128
web/modules/charts/modules/charts_highcharts/composer.json
Executable file
128
web/modules/charts/modules/charts_highcharts/composer.json
Executable file
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"name": "drupal/charts_highcharts",
|
||||
"description": "Charts module integration with Highcharts library.",
|
||||
"type": "drupal-module",
|
||||
"homepage": "http://drupal.org/project/charts",
|
||||
"support": {
|
||||
"issues": "https://www.drupal.org/project/issues/charts"
|
||||
},
|
||||
"license": "GPL-2.0+",
|
||||
"require": {
|
||||
"highcharts/3d": "11.1.0",
|
||||
"highcharts/accessibility": "11.1.0",
|
||||
"highcharts/export-data": "11.1.0",
|
||||
"highcharts/exporting": "11.1.0",
|
||||
"highcharts/highcharts": "11.1.0",
|
||||
"highcharts/more": "11.1.0"
|
||||
},
|
||||
"repositories": {
|
||||
"highcharts": {
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "highcharts/highcharts",
|
||||
"version": "11.1.0",
|
||||
"type": "drupal-library",
|
||||
"extra": {
|
||||
"installer-name": "highcharts"
|
||||
},
|
||||
"dist": {
|
||||
"url": "https://code.highcharts.com/11.1.0/highcharts.js",
|
||||
"type": "file"
|
||||
},
|
||||
"require": {
|
||||
"composer/installers": "^1.0 || ^2.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"highcharts_3d": {
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "highcharts/3d",
|
||||
"version": "11.1.0",
|
||||
"type": "drupal-library",
|
||||
"extra": {
|
||||
"installer-name": "highcharts_3d"
|
||||
},
|
||||
"dist": {
|
||||
"url": "https://code.highcharts.com/11.1.0/highcharts-3d.js",
|
||||
"type": "file"
|
||||
},
|
||||
"require": {
|
||||
"composer/installers": "^1.0 || ^2.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"highcharts_accessibility": {
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "highcharts/accessibility",
|
||||
"version": "11.1.0",
|
||||
"type": "drupal-library",
|
||||
"extra": {
|
||||
"installer-name": "highcharts_accessibility"
|
||||
},
|
||||
"dist": {
|
||||
"url": "https://code.highcharts.com/11.1.0/modules/accessibility.js",
|
||||
"type": "file"
|
||||
},
|
||||
"require": {
|
||||
"composer/installers": "^1.0 || ^2.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"highcharts_export_data": {
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "highcharts/export-data",
|
||||
"version": "11.1.0",
|
||||
"type": "drupal-library",
|
||||
"extra": {
|
||||
"installer-name": "highcharts_export-data"
|
||||
},
|
||||
"dist": {
|
||||
"url": "https://code.highcharts.com/11.1.0/modules/export-data.js",
|
||||
"type": "file"
|
||||
},
|
||||
"require": {
|
||||
"composer/installers": "^1.0 || ^2.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"highcharts_exporting": {
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "highcharts/exporting",
|
||||
"version": "11.1.0",
|
||||
"type": "drupal-library",
|
||||
"extra": {
|
||||
"installer-name": "highcharts_exporting"
|
||||
},
|
||||
"dist": {
|
||||
"url": "https://code.highcharts.com/11.1.0/modules/exporting.js",
|
||||
"type": "file"
|
||||
},
|
||||
"require": {
|
||||
"composer/installers": "^1.0 || ^2.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"highcharts_more": {
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "highcharts/more",
|
||||
"version": "11.1.0",
|
||||
"type": "drupal-library",
|
||||
"extra": {
|
||||
"installer-name": "highcharts_more"
|
||||
},
|
||||
"dist": {
|
||||
"url": "https://code.highcharts.com/11.1.0/highcharts-more.js",
|
||||
"type": "file"
|
||||
},
|
||||
"require": {
|
||||
"composer/installers": "^1.0 || ^2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
charts.settings.library_plugin.highcharts:
|
||||
type: charts_settings_library_plugin
|
||||
mapping:
|
||||
legend:
|
||||
label: Legend
|
||||
type: mapping
|
||||
mapping:
|
||||
layout:
|
||||
label: Layout
|
||||
type: string
|
||||
background_color:
|
||||
label: 'Background color'
|
||||
type: string
|
||||
border_width:
|
||||
label: 'Border width'
|
||||
type: string
|
||||
item_style:
|
||||
label: 'Border width'
|
||||
type: mapping
|
||||
mapping:
|
||||
color:
|
||||
label: Color
|
||||
type: string
|
||||
overflow:
|
||||
label: 'Text overflow'
|
||||
type: string
|
||||
shadow:
|
||||
label: 'Enable legend shadow'
|
||||
type: boolean
|
||||
exporting_library:
|
||||
label: 'Enable exporting library'
|
||||
type: boolean
|
||||
texture_library:
|
||||
label: 'Enable texture library'
|
||||
type: boolean
|
||||
global_options:
|
||||
label: 'Global options'
|
||||
type: mapping
|
||||
mapping:
|
||||
lang:
|
||||
type: mapping
|
||||
label: Language
|
||||
mapping:
|
||||
download_CSV:
|
||||
type: label
|
||||
label: 'Download CSV'
|
||||
download_JPEG:
|
||||
type: label
|
||||
label: 'Download JPEG'
|
||||
download_PDF:
|
||||
type: label
|
||||
label: 'Download PDF'
|
||||
download_PNG:
|
||||
type: label
|
||||
label: 'Download PNG'
|
||||
download_SVG:
|
||||
type: label
|
||||
label: 'Download SVG'
|
||||
download_XLS:
|
||||
type: label
|
||||
label: 'Download XLS'
|
||||
exit_fullscreen:
|
||||
type: label
|
||||
label: 'Exit fullscreen'
|
||||
hide_data:
|
||||
type: label
|
||||
label: 'Hide data'
|
||||
loading:
|
||||
type: label
|
||||
label: 'Loading'
|
||||
main_breadcrumb:
|
||||
type: label
|
||||
label: 'Main breadcrumb'
|
||||
thousands_sep:
|
||||
type: label
|
||||
label: 'Number formatting: Thousand separator'
|
||||
decimal_point:
|
||||
type: label
|
||||
label: 'Number formatting: Decimal point'
|
||||
no_data:
|
||||
type: label
|
||||
label: 'No data'
|
||||
print_chart:
|
||||
type: label
|
||||
label: 'Print chart'
|
||||
reset_zoom:
|
||||
type: label
|
||||
label: 'Reset zoom'
|
||||
reset_zoom_title:
|
||||
type: label
|
||||
label: 'Reset zoom title'
|
||||
view_data:
|
||||
type: label
|
||||
label: 'View data'
|
||||
view_fullscreen:
|
||||
type: label
|
||||
label: 'View fullscreen'
|
||||
context_button_title:
|
||||
type: label
|
||||
label: 'Context button title'
|
||||
drill_up_text:
|
||||
type: label
|
||||
label: 'Drill up text'
|
||||
invalid_date:
|
||||
type: label
|
||||
label: 'Invalid date'
|
||||
months:
|
||||
type: sequence
|
||||
label: Months
|
||||
sequence:
|
||||
type: label
|
||||
label: Month
|
||||
short_months:
|
||||
type: sequence
|
||||
label: 'Short Months'
|
||||
sequence:
|
||||
type: label
|
||||
label: 'Short Month'
|
||||
weekdays:
|
||||
type: sequence
|
||||
label: Weekdays
|
||||
sequence:
|
||||
type: label
|
||||
label: Weekday
|
||||
short_weekdays:
|
||||
type: sequence
|
||||
label: 'Short Weekdays'
|
||||
sequence:
|
||||
type: label
|
||||
label: 'Short Weekday'
|
||||
export_data:
|
||||
type: mapping
|
||||
label: 'Export data'
|
||||
mapping:
|
||||
annotation_header:
|
||||
type: label
|
||||
label: 'The annotation column title'
|
||||
category_datetime_header:
|
||||
type: label
|
||||
label: 'The category column title when axis type set to "datetime"'
|
||||
category_header:
|
||||
type: label
|
||||
label: 'The category column title'
|
||||
numeric_symbols:
|
||||
type: sequence
|
||||
label: 'Numeric symbols'
|
||||
sequence:
|
||||
type: label
|
||||
label: 'Numeric symbol'
|
||||
40
web/modules/charts/modules/charts_highcharts/js/charts_highcharts.js
Executable file
40
web/modules/charts/modules/charts_highcharts/js/charts_highcharts.js
Executable file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* @file
|
||||
* JavaScript's integration between Highcharts and Drupal.
|
||||
*/
|
||||
(function (Drupal, once, drupalSettings) {
|
||||
|
||||
'use strict';
|
||||
|
||||
Drupal.behaviors.chartsHighcharts = {
|
||||
attach: function (context) {
|
||||
const contents = new Drupal.Charts.Contents();
|
||||
if (drupalSettings.hasOwnProperty('charts') && drupalSettings.charts.highcharts.global_options) {
|
||||
Highcharts.setOptions(drupalSettings.charts.highcharts.global_options);
|
||||
}
|
||||
once('charts-highchart', '.charts-highchart', context).forEach(function (element) {
|
||||
const id = element.id;
|
||||
let config = contents.getData(id);
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
|
||||
config.chart.renderTo = id;
|
||||
new Highcharts.Chart(config);
|
||||
if (element.nextElementSibling && element.nextElementSibling.hasAttribute('data-charts-debug-container')) {
|
||||
element.nextElementSibling.querySelector('code').innerText = JSON.stringify(config, null, ' ');
|
||||
}
|
||||
});
|
||||
},
|
||||
detach: function (context, settings, trigger) {
|
||||
if (trigger === 'unload') {
|
||||
once('charts-highchart-detach', '.charts-highchart', context).forEach(function (element) {
|
||||
if (!element.dataset.hasOwnProperty('highchartsChart')) {
|
||||
return;
|
||||
}
|
||||
Highcharts.charts[element.dataset.highchartsChart].destroy();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}(Drupal, once, drupalSettings));
|
||||
42
web/modules/charts/modules/charts_highcharts/js/color_changer.js
Executable file
42
web/modules/charts/modules/charts_highcharts/js/color_changer.js
Executable file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* @file
|
||||
* JavaScript's integration between Highcharts and Drupal.
|
||||
*/
|
||||
((Drupal, once) => {
|
||||
'use strict';
|
||||
|
||||
Drupal.behaviors.chartsHighchartsColorChanger = {
|
||||
attach: function (context) {
|
||||
const colorChangerHandler = function (event) {
|
||||
const chartMetadata = JSON.parse(this.dataset.chartsHighchartsColorInfo);
|
||||
const chartsElement = document.getElementById(chartMetadata.chart_id);
|
||||
const chart = Highcharts.charts[chartsElement.dataset.highchartsChart];
|
||||
switch (chartMetadata.chart_type) {
|
||||
case 'pie':
|
||||
chart.series[0].data[chartMetadata.series_index].color = '';
|
||||
chart.series[0].data[chartMetadata.series_index].update({
|
||||
color: event.target.value
|
||||
});
|
||||
break;
|
||||
|
||||
case 'gauge':
|
||||
chart.yAxis[0].plotLinesAndBands[0].options.color = event.target.value;
|
||||
chart.yAxis[0].update();
|
||||
break;
|
||||
|
||||
default:
|
||||
chart.series[chartMetadata.series_index].update({
|
||||
color: event.target.value
|
||||
});
|
||||
}
|
||||
};
|
||||
once('charts-color-changer', '.charts-color-changer', context).forEach(function (element) {
|
||||
element.querySelectorAll('input[type="color"]').forEach(
|
||||
(colorChanger) => {
|
||||
colorChanger.addEventListener('change', colorChangerHandler);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
})(Drupal, once);
|
||||
69
web/modules/charts/modules/charts_highcharts/js/textures.js
Executable file
69
web/modules/charts/modules/charts_highcharts/js/textures.js
Executable file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* @file
|
||||
* Applies textures to Highcharts charts.
|
||||
*/
|
||||
(function (Drupal, once) {
|
||||
|
||||
'use strict';
|
||||
Drupal.charts_highcharts = Drupal.charts_highcharts || {};
|
||||
|
||||
Drupal.behaviors.chartsHighchartsAddTexture = {
|
||||
attach: function (context) {
|
||||
once('charts-highchart-texture', '.charts-highchart', context).forEach(function (element) {
|
||||
element.addEventListener('drupalChartsConfigsInitialization', function (e) {
|
||||
let data = e.detail;
|
||||
const id = data.drupalChartDivId;
|
||||
// Add textures to series.
|
||||
if ('series' in data && data.series[0].color !== undefined && typeof data.series[0].data[0] === "number") {
|
||||
for (let i = 0; i < data.series.length; i++) {
|
||||
data.series[i].color = Drupal.charts_highcharts.getPattern(Drupal.charts_highcharts.getUnderTenIndex(i), data.series[i].color);
|
||||
}
|
||||
} else if ('series' in data && typeof data.series[0].data[0] === "object" && data.series[0].data[0].color !== undefined) {
|
||||
for (let i = 0; i < data.series[0].data.length; i++) {
|
||||
data.series[0].data[i].color = Drupal.charts_highcharts.getPattern(Drupal.charts_highcharts.getUnderTenIndex(i), data.series[0].data[i].color);
|
||||
}
|
||||
}
|
||||
if ('series' in data && typeof data.series[0].data[0] === "object" && data.series[0].data[0].color === undefined) {
|
||||
for (let i = 0; i < data.colors.length; i++) {
|
||||
data.colors[i] = Drupal.charts_highcharts.getPattern(Drupal.charts_highcharts.getUnderTenIndex(i), data.colors[i]);
|
||||
}
|
||||
}
|
||||
|
||||
Drupal.Charts.Contents.update(id, data);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get under ten index in case there are more than ten series because
|
||||
* Highcharts patterns array has ten patterns
|
||||
* 0 - 9 index.
|
||||
*/
|
||||
Drupal.charts_highcharts.getUnderTenIndex = function (k) {
|
||||
if (k < 10) {
|
||||
return k;
|
||||
} else {
|
||||
while (k >= 10) {
|
||||
// Sum the digits of k.
|
||||
k = k.toString().split('').reduce(function (a, b) {
|
||||
return parseInt(a) + parseInt(b);
|
||||
});
|
||||
}
|
||||
return k;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a default pattern, but using the series color.
|
||||
* The index-argument refers to which default pattern to use
|
||||
*/
|
||||
Drupal.charts_highcharts.getPattern = function (index, color) {
|
||||
return {
|
||||
pattern: Highcharts.merge(Highcharts.patterns[index], {
|
||||
color: color
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
}(Drupal, once));
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\charts_highcharts\Form;
|
||||
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Implements a color-changer form.
|
||||
*/
|
||||
class ColorChanger extends FormBase {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'charts_highcharts_color_changer';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$chart_id = $form_state->get('chart_id');
|
||||
$series = $form_state->get('chart_series');
|
||||
$chart_type = $form_state->get('chart_type');
|
||||
$y_axis = $form_state->get('y_axis');
|
||||
if (!$series || !$chart_id) {
|
||||
return $form;
|
||||
}
|
||||
$form['#attributes']['class'][] = 'charts-color-changer';
|
||||
$form['color_changer_wrapper'] = [
|
||||
'#title' => $this->t('Change the colors'),
|
||||
'#type' => 'fieldset',
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => FALSE,
|
||||
];
|
||||
foreach ($series as $index => $item) {
|
||||
if ($chart_type === 'pie') {
|
||||
foreach ($item['data'] as $datum_index => $datum_value) {
|
||||
$form['color_changer_wrapper']['color_' . $datum_index] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('@label Color', ['@label' => $datum_value['name'] ?? '']),
|
||||
'#attributes' => [
|
||||
'TYPE' => 'color',
|
||||
'style' => 'min-width:50px;',
|
||||
'data-charts-highcharts-color-info' => json_encode([
|
||||
'series_index' => $datum_index,
|
||||
'chart_id' => $chart_id,
|
||||
'chart_type' => $chart_type,
|
||||
]),
|
||||
],
|
||||
'#size' => 10,
|
||||
'#maxlength' => 7,
|
||||
'#default_value' => $datum_value['color'] ?? '#000',
|
||||
];
|
||||
}
|
||||
}
|
||||
elseif ($chart_type === 'gauge') {
|
||||
$gauge_colors = [
|
||||
'red' => '#ff0000',
|
||||
'yellow' => '#ffff00',
|
||||
'green' => '#008000',
|
||||
];
|
||||
foreach ($y_axis['plotBands'] as $datum_index => $datum_value) {
|
||||
$form['color_changer_wrapper']['color_' . $datum_index] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('@label Color', ['@label' => $item['name'] . ' (' . $datum_value['color'] . ')']),
|
||||
'#attributes' => [
|
||||
'TYPE' => 'color',
|
||||
'style' => 'min-width:50px;',
|
||||
'data-charts-highcharts-color-info' => json_encode([
|
||||
'series_index' => $datum_index,
|
||||
'chart_id' => $chart_id,
|
||||
'chart_type' => $chart_type,
|
||||
]),
|
||||
],
|
||||
'#size' => 10,
|
||||
'#maxlength' => 7,
|
||||
'#default_value' => $gauge_colors[$datum_value['color']] ?? '#000',
|
||||
];
|
||||
}
|
||||
}
|
||||
else {
|
||||
$form['color_changer_wrapper']['color_' . $index] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('@label Color', ['@label' => $item['name']]),
|
||||
'#attributes' => [
|
||||
'TYPE' => 'color',
|
||||
'style' => 'min-width:50px;',
|
||||
'data-charts-highcharts-color-info' => json_encode([
|
||||
'series_index' => $index,
|
||||
'chart_id' => $chart_id,
|
||||
]),
|
||||
],
|
||||
'#size' => 10,
|
||||
'#maxlength' => 7,
|
||||
'#default_value' => $item['color'] ?? '#000',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {}
|
||||
|
||||
}
|
||||
1079
web/modules/charts/modules/charts_highcharts/src/Plugin/chart/Library/Highcharts.php
Executable file
1079
web/modules/charts/modules/charts_highcharts/src/Plugin/chart/Library/Highcharts.php
Executable file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\charts_highcharts\Kernel;
|
||||
|
||||
use Drupal\Tests\charts\Kernel\ChartsKernelTestBase;
|
||||
|
||||
/**
|
||||
* Test the raw_options element property behavior.
|
||||
*
|
||||
* @group charts
|
||||
*/
|
||||
class RawOptionsTest extends ChartsKernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'charts',
|
||||
'charts_highcharts',
|
||||
];
|
||||
|
||||
/**
|
||||
* Test raw options set on the main chart element.
|
||||
*/
|
||||
public function testMergeDeepArrayInPopulateOptions() {
|
||||
$series = [
|
||||
'#type' => 'chart_data',
|
||||
'#title' => '5.0.x',
|
||||
'#data' => [257, 235, 325, 340],
|
||||
'#color' => '#1d84c3',
|
||||
];
|
||||
$element = [
|
||||
'#type' => 'chart',
|
||||
'#chart_type' => 'column',
|
||||
'series' => $series,
|
||||
'#raw_options' => [
|
||||
'plotOptions' => [
|
||||
'series' => ['grouping' => TRUE],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// Testing raw options when there is NO data already set in the definition.
|
||||
$path = ['plotOptions', 'series', 'grouping'];
|
||||
$this->assertJsonPropertyHasValue($element, $path, TRUE);
|
||||
|
||||
// Testing raw options when there is data already set in the definition.
|
||||
$element['#data_labels'] = TRUE;
|
||||
$element['#raw_options'] = [
|
||||
'plotOptions' => [
|
||||
'series' => [
|
||||
'dataLabels' => [
|
||||
'enabled' => FALSE,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
$path = ['plotOptions', 'series', 'dataLabels', 'enabled'];
|
||||
$this->assertJsonPropertyHasValue($element, $path, FALSE);
|
||||
}
|
||||
|
||||
}
|
||||
35
web/modules/charts/src/Annotation/Chart.php
Executable file
35
web/modules/charts/src/Annotation/Chart.php
Executable file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\charts\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Defines a Chart annotation object.
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class Chart extends Plugin {
|
||||
|
||||
/**
|
||||
* The plugin ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The plugin name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* An array of chart types the chart library supports.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $types = [];
|
||||
|
||||
}
|
||||
34
web/modules/charts/src/ChartManager.php
Executable file
34
web/modules/charts/src/ChartManager.php
Executable file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\charts;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
|
||||
/**
|
||||
* Chart Manager.
|
||||
*
|
||||
* Provides the Chart plugin manager and manages discovery and instantiation of
|
||||
* chart plugins.
|
||||
*/
|
||||
class ChartManager extends DefaultPluginManager {
|
||||
|
||||
/**
|
||||
* Constructor for ChartManager objects.
|
||||
*
|
||||
* @param \Traversable $namespaces
|
||||
* An object that implements \Traversable which contains the root paths
|
||||
* keyed by the corresponding namespace to look for plugin implementations.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* Cache backend instance to use.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler to invoke the alter hook with.
|
||||
*/
|
||||
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
|
||||
parent::__construct('Plugin/chart/Library', $namespaces, $module_handler, 'Drupal\charts\Plugin\chart\Library\ChartInterface', 'Drupal\charts\Annotation\Chart');
|
||||
|
||||
$this->alterInfo('charts_chart_library');
|
||||
}
|
||||
|
||||
}
|
||||
18
web/modules/charts/src/ChartViewsFieldInterface.php
Normal file
18
web/modules/charts/src/ChartViewsFieldInterface.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\charts;
|
||||
|
||||
/**
|
||||
* Defines the interface for the chart views field data type.
|
||||
*/
|
||||
interface ChartViewsFieldInterface {
|
||||
|
||||
/**
|
||||
* Get the chart field data type.
|
||||
*
|
||||
* @return string
|
||||
* The chart field data type.
|
||||
*/
|
||||
public function getChartFieldDataType(): string;
|
||||
|
||||
}
|
||||
20
web/modules/charts/src/ColorHelperTrait.php
Normal file
20
web/modules/charts/src/ColorHelperTrait.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\charts;
|
||||
|
||||
/**
|
||||
* Contains helper method to help with the generation of random color.
|
||||
*/
|
||||
trait ColorHelperTrait {
|
||||
|
||||
/**
|
||||
* Provide a random color.
|
||||
*
|
||||
* @return string
|
||||
* A random color.
|
||||
*/
|
||||
public static function randomColor(): string {
|
||||
return sprintf('#%06X', mt_rand(0, 0xFFFFFF));
|
||||
}
|
||||
|
||||
}
|
||||
346
web/modules/charts/src/ConfigUpdater.php
Normal file
346
web/modules/charts/src/ConfigUpdater.php
Normal file
@@ -0,0 +1,346 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\charts;
|
||||
|
||||
use Drupal\Component\Serialization\Yaml;
|
||||
use Drupal\Component\Utility\Color;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Extension\ModuleExtensionList;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Manage the updates and upgrades of settings.
|
||||
*/
|
||||
class ConfigUpdater implements ContainerInjectionInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The module extension list.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleExtensionList
|
||||
*/
|
||||
protected $moduleExtensionList;
|
||||
|
||||
/**
|
||||
* ConfigEntityUpdater constructor.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
* @param \Drupal\Core\Extension\ModuleExtensionList $extension_list_module
|
||||
* The module extension list.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler, ModuleExtensionList $extension_list_module) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->moduleExtensionList = $extension_list_module;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('module_handler'),
|
||||
$container->get('extension.list.module')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms legacy settings to newer setting architecture.
|
||||
*
|
||||
* @param array $old_settings
|
||||
* The old settings.
|
||||
* @param string $for
|
||||
* The configuration for which the transformation is being done.
|
||||
* Regular config or view.
|
||||
*
|
||||
* @return array
|
||||
* The new format settings.
|
||||
*/
|
||||
public function transformVersion3SettingsToNew(array &$old_settings, string $for = 'config') {
|
||||
$new_settings = [];
|
||||
$new_settings['fields']['stacking'] = !empty($old_settings['grouping']);
|
||||
$old_config_keys = $this->getLegacySettingsMappingKeys();
|
||||
foreach ($old_settings as $setting_id => $setting_value) {
|
||||
if (empty($old_config_keys[$setting_id])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$setting_key_map = $old_config_keys[$setting_id];
|
||||
$value = $this->transformBoolStringValueToBool($setting_value);
|
||||
// When a block setting belongs to the chart blocks we save it in a
|
||||
// new setting.
|
||||
if (substr($setting_key_map, 0, 7) === 'display') {
|
||||
// Stripping the 'display_' in front of the mapping key.
|
||||
$setting_key_map = substr($setting_key_map, 8, strlen($setting_key_map));
|
||||
if (substr($setting_key_map, 0, 10) === 'dimensions') {
|
||||
// Stripping dimensions_.
|
||||
$setting_key_map = substr($setting_key_map, 11, strlen($setting_key_map));
|
||||
$new_settings['display']['dimensions'][$setting_key_map] = $value;
|
||||
}
|
||||
elseif (substr($setting_key_map, 0, 5) === 'gauge') {
|
||||
// Stripping gauge_.
|
||||
$setting_key_map = substr($setting_key_map, 6, strlen($setting_key_map));
|
||||
$new_settings['display']['gauge'][$setting_key_map] = $value;
|
||||
}
|
||||
else {
|
||||
$new_settings['display'][$setting_key_map] = $value;
|
||||
}
|
||||
}
|
||||
elseif (substr($setting_key_map, 0, 5) === 'xaxis') {
|
||||
// Stripping xaxis_.
|
||||
$setting_key_map = substr($setting_key_map, 6, strlen($setting_key_map));
|
||||
$new_settings['xaxis'][$setting_key_map] = $value;
|
||||
}
|
||||
elseif (substr($setting_key_map, 0, 5) === 'yaxis') {
|
||||
// Stripping yaxis_.
|
||||
$setting_key_map = substr($setting_key_map, 6, strlen($setting_key_map));
|
||||
if (substr($setting_key_map, 0, 9) === 'secondary') {
|
||||
// Stripping gauge_.
|
||||
$setting_key_map = substr($setting_key_map, 10, strlen($setting_key_map));
|
||||
$new_settings['yaxis']['secondary'][$setting_key_map] = $value;
|
||||
}
|
||||
else {
|
||||
$new_settings['yaxis'][$setting_key_map] = $value;
|
||||
}
|
||||
}
|
||||
elseif (substr($setting_key_map, 0, 6) === 'fields') {
|
||||
// Stripping fields_.
|
||||
$setting_key_map = substr($setting_key_map, 7, strlen($setting_key_map));
|
||||
if ($setting_key_map === 'data_providers' && is_array($value)) {
|
||||
$data_providers = $new_settings['fields']['data_providers'] ?? [];
|
||||
if ($setting_id === 'data_fields' || $setting_id == 'field_colors') {
|
||||
$new_settings['fields']['data_providers'] = $this->transformLegacyFieldsDataProvidersToNew($data_providers, $value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$new_settings['fields'][$setting_key_map] = $value;
|
||||
}
|
||||
}
|
||||
elseif ($setting_key_map === 'grouping' && $new_settings['fields']['stacking']) {
|
||||
$new_settings[$setting_key_map] = [];
|
||||
}
|
||||
else {
|
||||
// We make sure that we handle the color unneeded array.
|
||||
$new_settings[$setting_key_map] = $setting_key_map !== 'color' ? $value : $value[0];
|
||||
}
|
||||
// Then we remove it from the main old settings tree.
|
||||
unset($old_settings[$setting_id]);
|
||||
}
|
||||
|
||||
// Allow other modules to alter the new settings.
|
||||
$this->moduleHandler->alter('charts_version3_to_new_settings_structure', $new_settings, $for, $this);
|
||||
return $new_settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the current default settings.
|
||||
*/
|
||||
public function initializedCurrentDefaultSettings() {
|
||||
$path = $this->moduleExtensionList->getPath('charts');
|
||||
$default_install_settings_file = $path . '/config/install/charts.settings.yml';
|
||||
$default_install_settings = Yaml::decode(file_get_contents($default_install_settings_file));
|
||||
|
||||
$new_settings = &$default_install_settings['charts_default_settings'];
|
||||
|
||||
// Allow other modules to alter the new settings.
|
||||
$for = 'config';
|
||||
$this->moduleHandler->alter('charts_version3_to_new_settings_structure', $new_settings, $for, $this);
|
||||
return $default_install_settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates settings from version 3 of views.
|
||||
*/
|
||||
public function updateExistingViewsVersion3ToNewSettings() {
|
||||
$view_storage = $this->entityTypeManager->getStorage('view');
|
||||
$view_ids = $view_storage->getQuery()
|
||||
->accessCheck(FALSE)
|
||||
->condition('display.*.display_options.style.type', 'chart', '=')
|
||||
->execute();
|
||||
|
||||
if (!$view_ids) {
|
||||
return 'Views: No views had a display set to a charts style.';
|
||||
}
|
||||
|
||||
$updated_views = [];
|
||||
foreach ($view_ids as $view_id) {
|
||||
/** @var \Drupal\views\ViewEntityInterface $view */
|
||||
if (!($view = $view_storage->load($view_id))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$changed = FALSE;
|
||||
$displays = $view->get('display');
|
||||
foreach ($displays as &$display) {
|
||||
$style = &$display['display_options']['style'];
|
||||
if ($style['type'] !== 'chart' || !isset($style['options']['field_colors']) || !isset($style['options']['fields']['table'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$changed = TRUE;
|
||||
// Removing this because it was set in version 3 but was not used for
|
||||
// anything.
|
||||
unset($style['options']['fields']);
|
||||
|
||||
$options = &$style['options'];
|
||||
$options = $this->transformVersion3SettingsToNew($options, 'view');
|
||||
$chart_settings_elements = [
|
||||
'library',
|
||||
'type',
|
||||
'fields',
|
||||
'display',
|
||||
'xaxis',
|
||||
'yaxis',
|
||||
];
|
||||
foreach ($options as $option_key => $option) {
|
||||
if (in_array($option_key, $chart_settings_elements)) {
|
||||
$options['chart_settings'][$option_key] = $option;
|
||||
unset($options[$option_key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($changed) {
|
||||
$view->set('display', $displays);
|
||||
$view->save();
|
||||
$updated_views[] = $view_id;
|
||||
}
|
||||
}
|
||||
|
||||
if ($updated_views) {
|
||||
return sprintf('Views: The following views were updated: %s', implode(', ', $updated_views));
|
||||
}
|
||||
return sprintf('Views: The following views(%s) with at least one display of charts style were loaded but not updated!', implode(', ', array_values($view_ids)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms boolean string value to real boolean.
|
||||
*
|
||||
* @param mixed $value
|
||||
* The value to be transformed.
|
||||
*
|
||||
* @return bool|mixed
|
||||
* The boolean value or the original passed value.
|
||||
*/
|
||||
public function transformBoolStringValueToBool($value) {
|
||||
if ($value === 'FALSE' || $value === 'false') {
|
||||
return FALSE;
|
||||
}
|
||||
elseif ($value === 'TRUE' || $value === 'true') {
|
||||
return TRUE;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms legacy fields data providers to new.
|
||||
*
|
||||
* @param array $data_providers
|
||||
* Data providers.
|
||||
* @param array $legacy_value
|
||||
* Legacy value.
|
||||
*
|
||||
* @return mixed
|
||||
* Data providers returned
|
||||
*/
|
||||
private function transformLegacyFieldsDataProvidersToNew(array $data_providers, array $legacy_value) {
|
||||
$default_weight = 0;
|
||||
foreach ($legacy_value as $field_id => $value) {
|
||||
if (Color::validateHex($value)) {
|
||||
$data_providers[$field_id]['color'] = $value;
|
||||
}
|
||||
else {
|
||||
$data_providers[$field_id]['enabled'] = !empty($value);
|
||||
}
|
||||
$data_providers[$field_id]['weight'] = $default_weight;
|
||||
$default_weight++;
|
||||
}
|
||||
return $data_providers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets legacy settings mapping keys.
|
||||
*
|
||||
* @return array
|
||||
* Legacy settings keys to newer ones mapping.
|
||||
*/
|
||||
private function getLegacySettingsMappingKeys() {
|
||||
return [
|
||||
'library' => 'library',
|
||||
'chart_library' => 'library',
|
||||
'type' => 'type',
|
||||
'chart_type' => 'type',
|
||||
'grouping' => 'grouping',
|
||||
'title' => 'display_title',
|
||||
'title_position' => 'display_title_position',
|
||||
'data_labels' => 'display_data_labels',
|
||||
'data_markers' => 'display_data_markers',
|
||||
'legend' => 'display_legend',
|
||||
'legend_position' => 'display_legend_position',
|
||||
'background' => 'display_background',
|
||||
'three_dimensional' => 'display_three_dimensional',
|
||||
'polar' => 'display_polar',
|
||||
'series' => 'series',
|
||||
'data' => 'data',
|
||||
'color' => 'color',
|
||||
'data_series' => 'data_series',
|
||||
'series_label' => 'series_label',
|
||||
'categories' => 'categories',
|
||||
'field_colors' => 'fields_data_providers',
|
||||
'tooltips' => 'display_tooltips',
|
||||
'tooltips_use_html' => 'display_tooltips_use_html',
|
||||
'width' => 'display_dimensions_width',
|
||||
'height' => 'display_dimensions_height',
|
||||
'width_units' => 'display_dimensions_width_units',
|
||||
'height_units' => 'display_dimensions_height_units',
|
||||
'colors' => 'display_colors',
|
||||
'xaxis_title' => 'xaxis_title',
|
||||
'xaxis_labels_rotation' => 'xaxis_labels_rotation',
|
||||
'yaxis_title' => 'yaxis_title',
|
||||
'yaxis_min' => 'yaxis_min',
|
||||
'yaxis_max' => 'yaxis_max',
|
||||
'yaxis_prefix' => 'yaxis_prefix',
|
||||
'yaxis_suffix' => 'yaxis_suffix',
|
||||
'yaxis_decimal_count' => 'yaxis_decimal_count',
|
||||
'yaxis_labels_rotation' => 'yaxis_labels_rotation',
|
||||
'inherit_yaxis' => 'yaxis_inherit',
|
||||
'secondary_yaxis_title' => 'yaxis_secondary_title',
|
||||
'secondary_yaxis_min' => 'yaxis_secondary_min',
|
||||
'secondary_yaxis_max' => 'yaxis_secondary_min',
|
||||
'secondary_yaxis_prefix' => 'yaxis_secondary_prefix',
|
||||
'secondary_yaxis_suffix' => 'yaxis_secondary_suffix',
|
||||
'secondary_yaxis_decimal_count' => 'yaxis_secondary_decimal_count',
|
||||
'secondary_yaxis_labels_rotation' => 'yaxis_secondary_labels_rotation',
|
||||
'green_from' => 'display_gauge_green_from',
|
||||
'green_to' => 'display_gauge_green_to',
|
||||
'red_from' => 'display_gauge_red_from',
|
||||
'red_to' => 'display_gauge_red_to',
|
||||
'yellow_from' => 'display_gauge_yellow_from',
|
||||
'yellow_to' => 'display_gauge_yellow_to',
|
||||
'max' => 'display_gauge_max',
|
||||
'min' => 'display_gauge_min',
|
||||
'allow_advanced_rendering' => 'fields_allow_advanced_rendering',
|
||||
'label_field' => 'fields_label',
|
||||
'data_fields' => 'fields_data_providers',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
87
web/modules/charts/src/DependenciesCalculatorTrait.php
Normal file
87
web/modules/charts/src/DependenciesCalculatorTrait.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\charts;
|
||||
|
||||
/**
|
||||
* Provide method to calculate dependencies for charts config.
|
||||
*/
|
||||
trait DependenciesCalculatorTrait {
|
||||
|
||||
/**
|
||||
* The chart library plugin manager.
|
||||
*
|
||||
* @var \Drupal\charts\ChartManager
|
||||
*/
|
||||
protected $chartPluginManager;
|
||||
|
||||
/**
|
||||
* The chart type plugin library manager.
|
||||
*
|
||||
* @var \Drupal\charts\TypeManager
|
||||
*/
|
||||
protected $chartTypePluginManager;
|
||||
|
||||
/**
|
||||
* Calculates the dependencies given the chart library and chart type.
|
||||
*
|
||||
* @param string $chart_library_id
|
||||
* The chart library plugin id.
|
||||
* @param string $chart_type_id
|
||||
* The chart type plugin id.
|
||||
*
|
||||
* @return array
|
||||
* The calculated dependencies.
|
||||
*
|
||||
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
|
||||
*/
|
||||
public function calculateDependencies(string $chart_library_id, string $chart_type_id):array {
|
||||
$calculated_dependencies = [];
|
||||
$dependent_modules = [];
|
||||
if ($chart_library_id) {
|
||||
// Getting charting library provider and set it as one of the
|
||||
// dependencies for the chart settings.
|
||||
$plugin_definition = $this->chartPluginManager()->getDefinition($chart_library_id);
|
||||
$dependent_modules = [$plugin_definition['provider']];
|
||||
}
|
||||
if ($chart_type_id) {
|
||||
// Do the same thing for the chart type unless it was added by the main
|
||||
// "charts" module.
|
||||
$plugin_definition = $this->chartTypePluginManager()->getDefinition($chart_type_id);
|
||||
$provider = $plugin_definition['provider'];
|
||||
if ($provider !== 'charts' && !in_array($provider, $dependent_modules)) {
|
||||
$dependent_modules[] = $provider;
|
||||
}
|
||||
}
|
||||
if ($dependent_modules) {
|
||||
$calculated_dependencies = ['module' => $dependent_modules];
|
||||
}
|
||||
return $calculated_dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the chart plugin manager if needed.
|
||||
*
|
||||
* @return \Drupal\charts\ChartManager
|
||||
* The chart manager plugin.
|
||||
*/
|
||||
private function chartPluginManager(): ChartManager {
|
||||
if (!isset($this->chartPluginManager)) {
|
||||
$this->chartPluginManager = \Drupal::service('plugin.manager.charts');
|
||||
}
|
||||
return $this->chartPluginManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the chart type plugin manager if needed.
|
||||
*
|
||||
* @return \Drupal\charts\TypeManager
|
||||
* The chart type manager plugin.
|
||||
*/
|
||||
private function chartTypePluginManager(): TypeManager {
|
||||
if (!isset($this->chartTypePluginManager)) {
|
||||
$this->chartTypePluginManager = \Drupal::service('plugin.manager.charts_type');
|
||||
}
|
||||
return $this->chartTypePluginManager;
|
||||
}
|
||||
|
||||
}
|
||||
1644
web/modules/charts/src/Element/BaseSettings.php
Executable file
1644
web/modules/charts/src/Element/BaseSettings.php
Executable file
File diff suppressed because it is too large
Load Diff
449
web/modules/charts/src/Element/Chart.php
Executable file
449
web/modules/charts/src/Element/Chart.php
Executable file
@@ -0,0 +1,449 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\charts\Element;
|
||||
|
||||
use Drupal\charts\ChartManager;
|
||||
use Drupal\charts\Plugin\chart\Library\ChartBase;
|
||||
use Drupal\charts\Plugin\chart\Library\ChartInterface;
|
||||
use Drupal\charts\TypeManager;
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Render\Element\RenderElementBase;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a chart render element.
|
||||
*
|
||||
* @RenderElement("chart")
|
||||
*/
|
||||
class Chart extends RenderElementBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The config factory service.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* The chart plugin manager.
|
||||
*
|
||||
* @var \Drupal\charts\ChartManager
|
||||
*/
|
||||
protected $chartsManager;
|
||||
|
||||
/**
|
||||
* The chart type info service.
|
||||
*
|
||||
* @var \Drupal\charts\Plugin\chart\Type\TypeInterface
|
||||
*/
|
||||
protected $chartsTypeManager;
|
||||
|
||||
/**
|
||||
* The module handler service.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* Constructs a Chart object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The config factory service.
|
||||
* @param \Drupal\charts\ChartManager $chart_manager
|
||||
* The chart plugin manager.
|
||||
* @param \Drupal\charts\TypeManager $type_manager
|
||||
* The chart type manager.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, ConfigFactoryInterface $config_factory, ChartManager $chart_manager, TypeManager $type_manager, ModuleHandlerInterface $module_handler) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->configFactory = $config_factory;
|
||||
$this->chartsManager = $chart_manager;
|
||||
$this->chartsTypeManager = $type_manager;
|
||||
$this->moduleHandler = $module_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('config.factory'),
|
||||
$container->get('plugin.manager.charts'),
|
||||
$container->get('plugin.manager.charts_type'),
|
||||
$container->get('module_handler')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInfo() {
|
||||
return [
|
||||
'#chart_type' => NULL,
|
||||
'#chart_library' => NULL,
|
||||
'#chart_id' => NULL,
|
||||
'#title' => NULL,
|
||||
'#title_color' => '#000',
|
||||
'#title_font_weight' => 'normal',
|
||||
'#title_font_style' => 'normal',
|
||||
'#title_font_size' => 14,
|
||||
'#title_position' => 'out',
|
||||
'#subtitle' => NULL,
|
||||
'#colors' => ChartBase::getDefaultColors(),
|
||||
'#font' => 'Arial',
|
||||
'#font_size' => 12,
|
||||
'#gauge' => [],
|
||||
'#background' => 'transparent',
|
||||
'#stacking' => NULL,
|
||||
'#color_changer' => FALSE,
|
||||
'#pre_render' => [
|
||||
[$this, 'preRender'],
|
||||
],
|
||||
'#tooltips' => TRUE,
|
||||
'#tooltips_use_html' => FALSE,
|
||||
'#data_labels' => FALSE,
|
||||
'#data_markers' => FALSE,
|
||||
'#legend' => TRUE,
|
||||
'#legend_title' => '',
|
||||
'#legend_title_font_weight' => 'bold',
|
||||
'#legend_title_font_style' => 'normal',
|
||||
'#legend_title_font_size' => '',
|
||||
'#legend_position' => 'right',
|
||||
'#legend_font_weight' => 'normal',
|
||||
'#legend_font_style' => 'normal',
|
||||
'#legend_font_size' => NULL,
|
||||
'#width' => NULL,
|
||||
'#height' => NULL,
|
||||
'#attributes' => [],
|
||||
'#chart_definition' => [],
|
||||
'#raw_options' => [],
|
||||
'#content_prefix' => [],
|
||||
'#content_suffix' => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Main #pre_render callback to expand a chart element.
|
||||
*
|
||||
* @param array $element
|
||||
* The element.
|
||||
*
|
||||
* @return array
|
||||
* The chart element.
|
||||
*
|
||||
* @throws \Drupal\Component\Plugin\Exception\PluginException
|
||||
*/
|
||||
public function preRender(array $element) {
|
||||
/** @var \Drupal\charts\Plugin\chart\Library\ChartInterface[] $definitions */
|
||||
$definitions = $this->chartsManager->getDefinitions();
|
||||
|
||||
if (!$definitions) {
|
||||
$element['#type'] = 'markup';
|
||||
$element['#markup'] = $this->t('No charting library found. Enable a charting module such as Google Charts or Highcharts.');
|
||||
return $element;
|
||||
}
|
||||
|
||||
// Ensure there's an x and y axis to provide defaults.
|
||||
$type_name = $element['#chart_type'];
|
||||
$type = $this->chartsTypeManager->getDefinition($type_name);
|
||||
if ($type && $type['axis'] === ChartInterface::DUAL_AXIS) {
|
||||
$children_types = [];
|
||||
foreach (Element::children($element) as $key) {
|
||||
$children_types[] = $element[$key]['#type'];
|
||||
}
|
||||
|
||||
if (!in_array('chart_xaxis', $children_types)) {
|
||||
$element['xaxis'] = ['#type' => 'chart_xaxis'];
|
||||
}
|
||||
if (!in_array('chart_yaxis', $children_types)) {
|
||||
$element['yaxis'] = ['#type' => 'chart_yaxis'];
|
||||
}
|
||||
}
|
||||
|
||||
self::castElementIntergerValues($element);
|
||||
|
||||
// Generic theme function assuming it will be suitable for most chart types.
|
||||
$element['#theme'] = 'charts_chart';
|
||||
// Allow the chart to be altered - @TODO use event dispatching if needed.
|
||||
$alter_hooks = ['chart'];
|
||||
$chart_id = $element['#chart_id'];
|
||||
if ($chart_id) {
|
||||
$alter_hooks[] = 'chart_' . $element['#chart_id'];
|
||||
}
|
||||
$this->moduleHandler->alter($alter_hooks, $element, $chart_id);
|
||||
|
||||
// Include the library-specific render callback via their plugin manager.
|
||||
// Use the first charting library if the requested library is not available.
|
||||
$library = $element['#chart_library'] ?? '';
|
||||
$library = $this->getLibrary($library);
|
||||
|
||||
$element['#chart_library'] = $library;
|
||||
$charts_settings = $this->configFactory->get('charts.settings');
|
||||
$plugin_configuration = $charts_settings->get('charts_default_settings.library_config') ?? [];
|
||||
/** @var \Drupal\charts\Plugin\chart\Library\ChartInterface $plugin */
|
||||
$plugin = $this->chartsManager->createInstance($library, $plugin_configuration);
|
||||
if (!$plugin->isSupportedChartType($type_name)) {
|
||||
// Chart type not supported by the library.
|
||||
throw new \LogicException(sprintf('The provided chart type "%s" is not supported by "%s" chart plugin library.', $type_name, $plugin->getChartName()));
|
||||
}
|
||||
|
||||
$element = $plugin->preRender($element);
|
||||
|
||||
if (!empty($element['#chart_definition'])) {
|
||||
$chart_definition = $element['#chart_definition'];
|
||||
unset($element['#chart_definition']);
|
||||
|
||||
// Allow the chart definition to be altered - @TODO use event dispatching
|
||||
// if needed.
|
||||
$alter_hooks = ['chart_definition'];
|
||||
if ($element['#chart_id']) {
|
||||
$alter_hooks[] = 'chart_definition_' . $chart_id;
|
||||
}
|
||||
$this->moduleHandler->alter($alter_hooks, $chart_definition, $element, $chart_id);
|
||||
// Set the element #chart_json property as a data-attribute.
|
||||
$element['#attributes']['data-chart'] = Json::encode($chart_definition);
|
||||
}
|
||||
|
||||
$element['#cache']['tags'][] = 'config:charts.settings';
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts recursively integer values.
|
||||
*
|
||||
* @param array $element
|
||||
* The element.
|
||||
*/
|
||||
public static function castElementIntergerValues(array &$element) {
|
||||
// Cast options to integers to avoid redundant library fixing problems.
|
||||
$integer_options = [
|
||||
// Chart options.
|
||||
'#title_font_size',
|
||||
'#font_size',
|
||||
'#legend_title_font_size',
|
||||
'#legend_font_size',
|
||||
'#width',
|
||||
'#height',
|
||||
// Axis options.
|
||||
'#title_font_size',
|
||||
'#labels_font_size',
|
||||
'#labels_rotation',
|
||||
'#max',
|
||||
'#min',
|
||||
// Data options.
|
||||
'#decimal_count',
|
||||
];
|
||||
|
||||
foreach ($element as $property_name => $value) {
|
||||
if (is_array($element[$property_name])) {
|
||||
self::castElementIntergerValues($element[$property_name]);
|
||||
}
|
||||
elseif ($property_name && in_array($property_name, $integer_options)) {
|
||||
$element[$property_name] = (is_null($element[$property_name]) || strlen($element[$property_name]) === 0) ? NULL : (int) $element[$property_name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trims out, recursively, empty options that aren't used.
|
||||
*
|
||||
* @param array $array
|
||||
* The array to trim.
|
||||
*/
|
||||
public static function trimArray(array &$array) {
|
||||
foreach ($array as $key => &$value) {
|
||||
if (is_array($value)) {
|
||||
self::trimArray($value);
|
||||
}
|
||||
elseif (is_null($value) || (is_array($value) && count($value) === 0)) {
|
||||
unset($array[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the library.
|
||||
*
|
||||
* @param string $library
|
||||
* The library.
|
||||
*
|
||||
* @return string
|
||||
* The library.
|
||||
*/
|
||||
private function getLibrary($library) {
|
||||
$definitions = $this->chartsManager->getDefinitions();
|
||||
if (!$library || $library === 'site_default') {
|
||||
$charts_settings = $this->configFactory->get('charts.settings');
|
||||
$default_settings_library = $charts_settings->get('charts_default_settings.library');
|
||||
$library = !empty($default_settings_library) ? $default_settings_library : key($definitions);
|
||||
}
|
||||
elseif (!isset($definitions[$library])) {
|
||||
$library = key($definitions);
|
||||
}
|
||||
|
||||
return $library;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the element.
|
||||
*
|
||||
* @param array $settings
|
||||
* The settings.
|
||||
* @param string $chart_id
|
||||
* The chart id.
|
||||
*
|
||||
* @return array
|
||||
* The element.
|
||||
*/
|
||||
public static function buildElement(array $settings, string $chart_id): array {
|
||||
$type = $settings['type'];
|
||||
$single_axis = in_array($type, ['pie', 'donut']);
|
||||
$display_colors = $settings['display']['colors'] ?? [];
|
||||
|
||||
$element = [
|
||||
'#type' => 'chart',
|
||||
'#chart_type' => $type,
|
||||
'#chart_library' => $settings['library'],
|
||||
'#title' => $settings['display']['title'],
|
||||
'#title_position' => $settings['display']['title_position'],
|
||||
'#subtitle' => $settings['display']['subtitle'] ?? '',
|
||||
'#tooltips' => $settings['display']['tooltips'] ?? [],
|
||||
'#data_labels' => $settings['display']['data_labels'] ?? FALSE,
|
||||
'#data_markers' => $settings['display']['data_markers'] ?? FALSE,
|
||||
'#colors' => $display_colors,
|
||||
'#background' => $settings['display']['background'] ?? 'transparent',
|
||||
'#three_dimensional' => $settings['display']['three_dimensional'] ?? FALSE,
|
||||
'#polar' => $settings['display']['polar'] ?? FALSE,
|
||||
'#legend' => !empty($settings['display']['legend_position']),
|
||||
'#legend_position' => $settings['display']['legend_position'] ?? '',
|
||||
'#gauge' => $settings['display']['gauge'] ?? [],
|
||||
'#stacking' => !empty($settings['display']['stacking']) ?? NULL,
|
||||
'#width' => $settings['display']['dimensions']['width'],
|
||||
'#height' => $settings['display']['dimensions']['height'],
|
||||
'#width_units' => $settings['display']['dimensions']['width_units'],
|
||||
'#height_units' => $settings['display']['dimensions']['height_units'],
|
||||
'#color_changer' => $settings['display']['color_changer'] ?? FALSE,
|
||||
];
|
||||
|
||||
if (empty($settings['series'])) {
|
||||
return $element;
|
||||
}
|
||||
|
||||
$table = $settings['series'];
|
||||
// Extracting the categories.
|
||||
$categories = ChartDataCollectorTable::getCategoriesFromCollectedTable($table, $type);
|
||||
// Extracting the rest of the data.
|
||||
$series_data = ChartDataCollectorTable::getSeriesFromCollectedTable($table, $type);
|
||||
|
||||
$element['xaxis'] = [
|
||||
'#type' => 'chart_xaxis',
|
||||
'#labels' => $single_axis ? '' : $categories['data'],
|
||||
'#title' => $settings['xaxis']['title'] ?? FALSE,
|
||||
'#labels_rotation' => $settings['xaxis']['labels_rotation'],
|
||||
];
|
||||
|
||||
if (empty($series_data)) {
|
||||
return $element;
|
||||
}
|
||||
|
||||
$element['yaxis'] = [
|
||||
'#type' => 'chart_yaxis',
|
||||
'#title' => $settings['yaxis']['title'] ?? '',
|
||||
'#labels_rotation' => $settings['yaxis']['labels_rotation'],
|
||||
'#max' => $settings['yaxis']['max'],
|
||||
'#min' => $settings['yaxis']['min'],
|
||||
'#prefix' => $settings['yaxis']['prefix'],
|
||||
'#suffix' => $settings['yaxis']['suffix'],
|
||||
'#decimal_count' => $settings['yaxis']['decimal_count'],
|
||||
];
|
||||
|
||||
// Create a secondary axis if needed.
|
||||
$series_count = count($series_data);
|
||||
if (!empty($settings['yaxis']['inherit']) && $series_count === 2) {
|
||||
$element['secondary_yaxis'] = [
|
||||
'#type' => 'chart_yaxis',
|
||||
'#title' => $settings['yaxis']['secondary']['title'] ?? '',
|
||||
'#labels_rotation' => $settings['yaxis']['secondary']['labels_rotation'],
|
||||
'#max' => $settings['yaxis']['secondary']['max'],
|
||||
'#min' => $settings['yaxis']['secondary']['min'],
|
||||
'#prefix' => $settings['yaxis']['secondary']['prefix'],
|
||||
'#suffix' => $settings['yaxis']['secondary']['suffix'],
|
||||
'#decimal_count' => $settings['yaxis']['secondary']['decimal_count'],
|
||||
'#opposite' => TRUE,
|
||||
];
|
||||
}
|
||||
|
||||
// Overriding element colors for pie and donut chart types when the
|
||||
// settings display colors is empty.
|
||||
$overrides_element_colors = !$display_colors && ($type === 'pie' || $type === 'donut');
|
||||
$series_key = $chart_id . '__series';
|
||||
if ($single_axis) {
|
||||
$new_series = [];
|
||||
$labels = [];
|
||||
foreach ($series_data as $datum) {
|
||||
$new_series[] = $datum['data'][0][1];
|
||||
$labels[] = $datum['name'];
|
||||
if ($overrides_element_colors) {
|
||||
$element['#colors'][] = $datum['color'];
|
||||
}
|
||||
}
|
||||
$element['xaxis']['#labels'] = $labels;
|
||||
// @todo Address more than one series.
|
||||
$element[$series_key] = [
|
||||
'#type' => 'chart_data',
|
||||
'#data' => $new_series,
|
||||
'#title' => $series_data[0]['title'] ?? '',
|
||||
];
|
||||
}
|
||||
else {
|
||||
$series_counter = 0;
|
||||
foreach ($series_data as $data_index => $data) {
|
||||
$key = $series_key . '__' . $data_index;
|
||||
$element[$key] = [
|
||||
'#type' => 'chart_data',
|
||||
'#data' => $data['data'],
|
||||
'#title' => $data['name'],
|
||||
];
|
||||
if (!empty($data['color'])) {
|
||||
$element[$key]['#color'] = $data['color'];
|
||||
}
|
||||
if (isset($element['yaxis'])) {
|
||||
$element[$key]['#prefix'] = $settings['yaxis']['prefix'];
|
||||
$element[$key]['#suffix'] = $settings['yaxis']['suffix'];
|
||||
$element[$key]['#decimal_count'] = $settings['yaxis']['decimal_count'];
|
||||
}
|
||||
if (isset($element['secondary_yaxis']) && $series_counter === 1) {
|
||||
$element[$key]['#target_axis'] = 'secondary_yaxis';
|
||||
$element[$key]['#prefix'] = $settings['yaxis']['secondary']['prefix'];
|
||||
$element[$key]['#suffix'] = $settings['yaxis']['secondary']['suffix'];
|
||||
$element[$key]['#decimal_count'] = $settings['yaxis']['secondary']['decimal_count'];
|
||||
}
|
||||
$series_counter++;
|
||||
}
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
}
|
||||
49
web/modules/charts/src/Element/ChartAxisBase.php
Executable file
49
web/modules/charts/src/Element/ChartAxisBase.php
Executable file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\charts\Element;
|
||||
|
||||
use Drupal\Core\Render\Element\RenderElementBase;
|
||||
|
||||
/**
|
||||
* Provides a chart render element.
|
||||
*/
|
||||
abstract class ChartAxisBase extends RenderElementBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInfo(): array {
|
||||
return [
|
||||
// Options: linear, logarithmic, datetime, labels.
|
||||
'#axis_type' => '',
|
||||
'#title' => '',
|
||||
'#title_color' => '#000',
|
||||
// Options: normal, bold.
|
||||
'#title_font_weight' => 'normal',
|
||||
// Options: normal, italic.
|
||||
'#title_font_style' => 'normal',
|
||||
'#title_font_size' => 12,
|
||||
// CSS value for font size, e.g. 1em or 12px.
|
||||
'#labels' => NULL,
|
||||
'#labels_color' => '#000',
|
||||
// Options: normal, bold.
|
||||
'#labels_font_weight' => 'normal',
|
||||
// Options: normal, italic.
|
||||
'#labels_font_style' => 'normal',
|
||||
// CSS value for font size, e.g. 1em or 12px.
|
||||
'#labels_font_size' => NULL,
|
||||
// Integer rotation value, e.g. 30, -60 or 90.
|
||||
'#labels_rotation' => NULL,
|
||||
'#grid_line_color' => '#ccc',
|
||||
'#base_line_color' => '#ccc',
|
||||
'#minor_grid_line_color' => '#e0e0e0',
|
||||
// Integer max value on this axis.
|
||||
'#max' => NULL,
|
||||
// Integer minimum value on this axis.
|
||||
'#min' => NULL,
|
||||
// Display axis on opposite normal side.
|
||||
'#opposite' => FALSE,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
44
web/modules/charts/src/Element/ChartData.php
Executable file
44
web/modules/charts/src/Element/ChartData.php
Executable file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\charts\Element;
|
||||
|
||||
use Drupal\Core\Render\Element\RenderElementBase;
|
||||
|
||||
/**
|
||||
* Provides a chart data render element.
|
||||
*
|
||||
* @RenderElement("chart_data")
|
||||
*/
|
||||
class ChartData extends RenderElementBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInfo(): array {
|
||||
return [
|
||||
'#title' => NULL,
|
||||
'#labels' => NULL,
|
||||
'#data' => [],
|
||||
'#color' => NULL,
|
||||
'#show_in_legend' => TRUE,
|
||||
// Show inline labels next to the data.
|
||||
'#show_labels' => FALSE,
|
||||
// If building multicharts. The chart type, e.g. pie.
|
||||
'#chart_type' => NULL,
|
||||
// Line chart only.
|
||||
'#line_width' => 1,
|
||||
// Line chart only. Size in pixels, e.g. 1, 5.
|
||||
'#marker_radius' => 3,
|
||||
// If using multiple axes, key for the matching y axis.
|
||||
'#target_axis' => NULL,
|
||||
// Formatting options.
|
||||
// The number of digits after the decimal separator. e.g. 2.
|
||||
'#decimal_count' => NULL,
|
||||
// A custom date format, e.g. %Y-%m-%d.
|
||||
'#date_format' => NULL,
|
||||
'#prefix' => NULL,
|
||||
'#suffix' => NULL,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
909
web/modules/charts/src/Element/ChartDataCollectorTable.php
Executable file
909
web/modules/charts/src/Element/ChartDataCollectorTable.php
Executable file
@@ -0,0 +1,909 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\charts\Element;
|
||||
|
||||
use Drupal\charts\ColorHelperTrait;
|
||||
use Drupal\charts\Plugin\chart\Library\ChartInterface;
|
||||
use Drupal\Component\Utility\Environment;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Render\Element\FormElementBase;
|
||||
|
||||
/**
|
||||
* Provides a chart data collector table form element.
|
||||
*
|
||||
* @FormElement("chart_data_collector_table")
|
||||
*/
|
||||
class ChartDataCollectorTable extends FormElementBase {
|
||||
|
||||
use ColorHelperTrait;
|
||||
use ElementFormStateTrait;
|
||||
|
||||
const FIRST_COLUMN = 'first_column';
|
||||
const FIRST_ROW = 'first_row';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInfo() {
|
||||
$class = get_class($this);
|
||||
return [
|
||||
'#input' => TRUE,
|
||||
// Either to enable csv import.
|
||||
'#import_csv' => TRUE,
|
||||
'#import_csv_separator' => ',',
|
||||
// The initial number of rows to generate.
|
||||
'#initial_rows' => 5,
|
||||
// The initial number of columns to generate.
|
||||
'#initial_columns' => 2,
|
||||
// The optional element the table should be wrapped in.
|
||||
'#table_wrapper' => '',
|
||||
'#table_wrapper_attributes' => [],
|
||||
'#table_attributes' => [],
|
||||
// Allows to toggle on/off drupal tabledrag functionality.
|
||||
'#table_drag' => TRUE,
|
||||
'#default_colors' => [],
|
||||
'#process' => [
|
||||
[$class, 'processDataCollectorTable'],
|
||||
],
|
||||
'#element_validate' => [
|
||||
[$class, 'validateDataCollectorTable'],
|
||||
],
|
||||
'#theme_wrappers' => ['container'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the element to render a table to collect a data for the chart.
|
||||
*
|
||||
* @param array $element
|
||||
* The element.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state.
|
||||
* @param array $complete_form
|
||||
* The complete form.
|
||||
*
|
||||
* @return array
|
||||
* The processed element.
|
||||
*/
|
||||
public static function processDataCollectorTable(array &$element, FormStateInterface $form_state, array &$complete_form) {
|
||||
$parents = $element['#parents'];
|
||||
$id_prefix = implode('-', $parents);
|
||||
$wrapper_id = Html::getUniqueId($id_prefix . '-ajax-wrapper');
|
||||
$value = $element['#value'];
|
||||
$required = !empty($element['#required']);
|
||||
$user_input = $form_state->getUserInput();
|
||||
|
||||
$element_state = self::getElementState($parents, $form_state);
|
||||
// Getting columns and rows count.
|
||||
if (empty($element_state['data_collector_table']) || empty($element_state['table_categories_identifier'])) {
|
||||
$identifier_value = $value['table_categories_identifier'] ?? self::FIRST_COLUMN;
|
||||
$element_state['table_categories_identifier'] = $identifier_value;
|
||||
$element_state['data_collector_table'] = $value['data_collector_table'] ?? [];
|
||||
$element_state['data_collector_table'] = $element_state['data_collector_table'] ?: self::initializeEmptyTable($element, $identifier_value);
|
||||
self::setElementState($parents, $form_state, $element_state);
|
||||
}
|
||||
else {
|
||||
// This is hack to make ajax call return the proper identifier.
|
||||
$element_state['table_categories_identifier'] = $value['table_categories_identifier'];
|
||||
}
|
||||
|
||||
// Enforce tree.
|
||||
$element = [
|
||||
'#tree' => TRUE,
|
||||
'#prefix' => '<div id="' . $wrapper_id . '">',
|
||||
'#suffix' => '</div>',
|
||||
// Pass the id along to other methods.
|
||||
'#wrapper_id' => $wrapper_id,
|
||||
] + $element;
|
||||
|
||||
$element['table_categories_identifier'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Categories are identified by'),
|
||||
'#options' => [
|
||||
self::FIRST_COLUMN => t('First column'),
|
||||
self::FIRST_ROW => t('First row'),
|
||||
],
|
||||
'#description' => t('Select whether the first row or column hold the categories data'),
|
||||
'#required' => $required,
|
||||
'#default_value' => $element_state['table_categories_identifier'],
|
||||
'#ajax' => [
|
||||
'callback' => [get_called_class(), 'ajaxRefresh'],
|
||||
'progress' => ['type' => 'throbber'],
|
||||
'wrapper' => $wrapper_id,
|
||||
'effect' => 'fade',
|
||||
],
|
||||
];
|
||||
|
||||
$table = [
|
||||
'#type' => 'table',
|
||||
'#tree' => TRUE,
|
||||
'#header' => [],
|
||||
'#responsive' => FALSE,
|
||||
'#attributes' => [
|
||||
'class' => ['data-collector-table'],
|
||||
],
|
||||
];
|
||||
|
||||
$table_drag = $element['#table_drag'];
|
||||
$table_drag_group = Html::cleanCssIdentifier($id_prefix . '-order-weight');
|
||||
if ($table_drag) {
|
||||
$table['#tabledrag'] = [
|
||||
[
|
||||
'action' => 'order',
|
||||
'relationship' => 'sibling',
|
||||
'group' => $table_drag_group,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if ($element['#table_wrapper'] === 'container') {
|
||||
$element['table_wrapper'] = [
|
||||
'#type' => 'container',
|
||||
'#attributes' => $element['#table_wrapper_attributes'],
|
||||
'#tree' => FALSE,
|
||||
];
|
||||
$element['table_wrapper']['data_collector_table'] = &$table;
|
||||
}
|
||||
else {
|
||||
$element['data_collector_table'] = &$table;
|
||||
}
|
||||
|
||||
$rows = count($element_state['data_collector_table']);
|
||||
|
||||
// Make the weight list always reflect the current number of values.
|
||||
$max_weight = count($element_state['data_collector_table']);
|
||||
$max_row = max(array_keys($element_state['data_collector_table']));
|
||||
|
||||
// The first column need to be for colors.
|
||||
$is_first_column = $element_state['table_categories_identifier'] === self::FIRST_COLUMN;
|
||||
$first_row_key = NULL;
|
||||
foreach ($element_state['data_collector_table'] as $i => $row) {
|
||||
$first_row_key = $first_row_key ?? $i;
|
||||
$table_first_row = $i === $first_row_key;
|
||||
$add_color_first_row = ($is_first_column && $table_first_row);
|
||||
$first_col_key = NULL;
|
||||
|
||||
$row_form = &$table[$i];
|
||||
$row_form['#attributes']['class'][] = 'data-collector-table--row';
|
||||
|
||||
// Adding the row textfield cells.
|
||||
foreach ($row as $j => $column) {
|
||||
if ($j === 'weight') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$first_col_key = $first_col_key ?? $j;
|
||||
$table_first_col = $j === $first_col_key;
|
||||
// To be used to skip color input on cell[0][0].
|
||||
$is_category_cell = $table_first_col && $table_first_row;
|
||||
$row_form[$j]['data'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Data for column @col - Row @row', [
|
||||
'@row' => $i,
|
||||
'@col' => $j,
|
||||
]),
|
||||
'#title_display' => 'invisible',
|
||||
'#size' => 10,
|
||||
'#default_value' => is_array($column) ? $column['data'] : $column,
|
||||
'#wrapper_attributes' => [
|
||||
'class' => ['data-collector-table--row--cell'],
|
||||
],
|
||||
];
|
||||
|
||||
if (!$is_category_cell && ($add_color_first_row || (!$is_first_column && $j === $first_col_key))) {
|
||||
if (empty($column['color'])) {
|
||||
$color_index = $is_first_column ? $j : $i;
|
||||
$column['color'] = $element['#default_colors'][$color_index - 1] ?? self::randomColor();
|
||||
}
|
||||
$row_form[$j]['#wrapper_attributes'] = [
|
||||
'class' => ['container-inline'],
|
||||
];
|
||||
$row_form[$j]['color'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Color'),
|
||||
'#title_display' => 'invisible',
|
||||
'#attributes' => [
|
||||
'TYPE' => 'color',
|
||||
'style' => 'min-width:50px;',
|
||||
],
|
||||
'#size' => 10,
|
||||
'#maxlength' => 7,
|
||||
'#default_value' => $column['color'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Adding weight if table drag enabled.
|
||||
if ($table_drag) {
|
||||
$row_form['#attributes']['class'][] = 'draggable';
|
||||
if (($i + 1) === $rows) {
|
||||
$default_weight = $max_weight;
|
||||
}
|
||||
else {
|
||||
$default_weight = $max_row + 1;
|
||||
}
|
||||
$row_form['weight'] = [
|
||||
'#type' => 'weight',
|
||||
'#title' => t('Weight'),
|
||||
'#title_display' => 'invisible',
|
||||
'#delta' => $max_weight,
|
||||
'#default_value' => $element_state['data_collector_table'][$i]['weight'] ?? $default_weight,
|
||||
'#attributes' => [
|
||||
'class' => [$table_drag_group],
|
||||
],
|
||||
];
|
||||
// Used by SortArray::sortByWeightProperty to sort the rows.
|
||||
if (isset($user_input['data_collector_table'][$i])) {
|
||||
$input_weight = $user_input['data_collector_table'][$i]['weight'];
|
||||
// Make sure the weight is not out of bounds due to removals.
|
||||
if ($user_input['data_collector_table'][$i]['weight'] > $max_weight) {
|
||||
$input_weight = $max_weight;
|
||||
}
|
||||
// Reflect the updated user input on the element.
|
||||
$row_form['weight']['#value'] = $input_weight;
|
||||
$row_form['#weight'] = $input_weight;
|
||||
}
|
||||
else {
|
||||
$row_form['#weight'] = $default_weight;
|
||||
}
|
||||
}
|
||||
|
||||
// Row delete button.
|
||||
$row_form['delete'] = self::buildOperationButton('delete', 'row', $id_prefix, $wrapper_id, $i, [], [
|
||||
'class' => ['data-collector-table--row--delete'],
|
||||
]);
|
||||
}
|
||||
|
||||
$colspan = 1;
|
||||
if ($table_drag) {
|
||||
// Sort the values by weight. Ensures weight is preserved on ajax refresh.
|
||||
uasort($table, [
|
||||
'\Drupal\Component\Utility\SortArray',
|
||||
'sortByWeightProperty',
|
||||
]);
|
||||
|
||||
// Increasing colspan when weight column is added.
|
||||
$colspan = 2;
|
||||
}
|
||||
|
||||
// Building the column delete button.
|
||||
$table['_delete_column_buttons'] = [
|
||||
'#attributes' => ['class' => ['data-collector-table--column-deletes-row']],
|
||||
];
|
||||
// Using first row to get the count of columns.
|
||||
$first_row = current($element_state['data_collector_table']);
|
||||
// Using array filter to exclude weight key when grabbing the row columns.
|
||||
$columns = self::excludeWeightColumnFromRow($first_row);
|
||||
$max_column = max(array_keys($first_row));
|
||||
foreach ($columns as $column) {
|
||||
$table['_delete_column_buttons'][$column] = self::buildOperationButton('delete', 'column', $id_prefix, $wrapper_id, $column, [], [
|
||||
'class' => ['data-collector-table--column--delete'],
|
||||
]);
|
||||
if ($column === $max_column) {
|
||||
$table['_delete_column_buttons'][$column]['#wrapper_attributes']['colspan'] = $colspan;
|
||||
}
|
||||
}
|
||||
// Empty Column under delete operation placeholder.
|
||||
$table['_delete_column_buttons'][$max_column + 1] = [
|
||||
'#markup' => '',
|
||||
];
|
||||
|
||||
// Footer operations.
|
||||
$table['_operations'] = [
|
||||
'#attributes' => ['class' => ['data-collector-table--operations-row']],
|
||||
];
|
||||
$table['_operations']['wrapper'] = [
|
||||
'#type' => 'container',
|
||||
'#wrapper_attributes' => [
|
||||
'colspan' => count($columns) + $colspan,
|
||||
],
|
||||
];
|
||||
$table['_operations']['wrapper']['add_column'] = self::buildOperationButton('add', 'column', $id_prefix, $wrapper_id, NULL);
|
||||
$table['_operations']['wrapper']['add_row'] = self::buildOperationButton('add', 'row', $id_prefix, $wrapper_id, NULL);
|
||||
|
||||
if ($element['#import_csv']) {
|
||||
$element['import'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => t('Import Data from CSV'),
|
||||
'#description' => t('Note importing data from CSV will overwrite all the current data entry in the table.'),
|
||||
'#open' => FALSE,
|
||||
];
|
||||
$element['import']['csv_separator'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('CSV separator'),
|
||||
'#default_value' => $element['#import_csv_separator'] ?? ',',
|
||||
'#size' => 1,
|
||||
'#required' => TRUE,
|
||||
];
|
||||
$element['import']['csv'] = [
|
||||
'#name' => 'files[' . $id_prefix . ']',
|
||||
'#title' => t('File upload'),
|
||||
'#title_display' => 'invisible',
|
||||
'#type' => 'file',
|
||||
'#upload_validators' => [
|
||||
'file_validate_extensions' => ['csv'],
|
||||
'file_validate_size' => [Environment::getUploadMaxSize()],
|
||||
],
|
||||
];
|
||||
$element['import']['upload'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Upload CSV'),
|
||||
'#name' => $id_prefix . '-import-csv',
|
||||
'#attributes' => [
|
||||
'class' => [Html::cleanCssIdentifier($id_prefix . '--import-csv')],
|
||||
],
|
||||
'#submit' => [[get_called_class(), 'importCsvToTableSubmit']],
|
||||
'#limit_validation_errors' => [
|
||||
array_merge($parents, ['import', 'csv']),
|
||||
array_merge($parents, ['import', 'upload']),
|
||||
],
|
||||
'#ajax' => [
|
||||
'callback' => [get_called_class(), 'ajaxRefresh'],
|
||||
'progress' => ['type' => 'throbber'],
|
||||
'wrapper' => $wrapper_id,
|
||||
'effect' => 'fade',
|
||||
],
|
||||
'#operation' => 'csv',
|
||||
'#csv_separator' => $element['#import_csv_separator'] ?? ',',
|
||||
];
|
||||
}
|
||||
$element['#attributes']['style'] = 'overflow: auto;';
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the data collected.
|
||||
*
|
||||
* @param array $element
|
||||
* The form element.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*/
|
||||
public static function validateDataCollectorTable(array $element, FormStateInterface $form_state) {
|
||||
$parents = $element['#parents'];
|
||||
$value = $form_state->getValue($parents);
|
||||
|
||||
// Remove empty rows and unneeded keys.
|
||||
foreach ($value['data_collector_table'] as $row_key => $row) {
|
||||
if (!is_numeric($row_key)) {
|
||||
unset($value['data_collector_table'][$row_key]);
|
||||
continue;
|
||||
}
|
||||
foreach ($row as $column_key => $column) {
|
||||
if (!is_numeric($column_key)) {
|
||||
unset($value['data_collector_table'][$row_key][$column_key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($value['import']);
|
||||
$form_state->setValue($parents, $value);
|
||||
|
||||
if ($element['#required'] && empty($value['table_categories_identifier'])) {
|
||||
$form_state->setError($element['table_categories_identifier'], t('Please select how categories should be identified.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax callback.
|
||||
*/
|
||||
public static function ajaxRefresh(array $form, FormStateInterface $form_state) {
|
||||
$triggering_element = $form_state->getTriggeringElement();
|
||||
$operation = $triggering_element['#operation'] ?? '';
|
||||
|
||||
if ($operation === 'csv' || (!$operation && $triggering_element['#type'] === 'radio')) {
|
||||
$length = -2;
|
||||
}
|
||||
else {
|
||||
$length = $operation === 'add' ? -4 : -3;
|
||||
}
|
||||
$element_parents = array_slice($triggering_element['#array_parents'], 0, $length);
|
||||
return NestedArray::getValue($form, $element_parents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit callback for table add and delete operations.
|
||||
*/
|
||||
public static function tableOperationSubmit(array $form, FormStateInterface $form_state) {
|
||||
$triggering_element = $form_state->getTriggeringElement();
|
||||
$operation_on = $triggering_element['#operation_on'];
|
||||
$operation = $triggering_element['#operation'];
|
||||
$length = $operation === 'add' ? -4 : -3;
|
||||
$element_parents = array_slice($triggering_element['#parents'], 0, $length);
|
||||
if (!$element_parents) {
|
||||
$length = $operation == 'add' ? -4 : -3;
|
||||
$element_parents = array_slice($triggering_element['#array_parents'], 0, $length);
|
||||
}
|
||||
$element_state = self::getElementState($element_parents, $form_state);
|
||||
$index = $triggering_element['#' . $operation_on . '_index'] ?? NULL;
|
||||
|
||||
if ($operation_on === 'row') {
|
||||
$element_state = self::tableRowOperation($element_state, $form_state, $operation, $index);
|
||||
}
|
||||
else {
|
||||
$element_state = self::tableColumnOperation($element_state, $form_state, $operation, $element_parents, $index);
|
||||
}
|
||||
|
||||
self::setElementState($element_parents, $form_state, $element_state);
|
||||
$form_state->setRebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit callback for table csv import operations.
|
||||
*/
|
||||
public static function importCsvToTableSubmit(array $form, FormStateInterface $form_state) {
|
||||
$triggering_element = $form_state->getTriggeringElement();
|
||||
$element_parents = array_slice($triggering_element['#parents'], 0, -2);
|
||||
$id_prefix = implode('-', $element_parents);
|
||||
$files = \Drupal::request()->files->get('files');
|
||||
/** @var \Symfony\Component\HttpFoundation\File\UploadedFile $file_upload */
|
||||
$file_upload = $files[$id_prefix];
|
||||
|
||||
$handle = $file_upload ? fopen($file_upload->getPathname(), 'r') : NULL;
|
||||
if ($handle) {
|
||||
// Checking the encoding of the CSV file to be UTF-8.
|
||||
$encoding = 'UTF-8';
|
||||
if (function_exists('mb_detect_encoding')) {
|
||||
$file_contents = file_get_contents($file_upload->getPathname());
|
||||
$encodings = ['UTF-8', 'ISO-8859-1', 'WINDOWS-1251'];
|
||||
$encodings_list = implode(',', $encodings);
|
||||
$encoding = mb_detect_encoding($file_contents, $encodings_list);
|
||||
}
|
||||
|
||||
// Populate CSV values.
|
||||
$rows_count = 0;
|
||||
$element_state = [];
|
||||
$user_inputs = $form_state->getUserInput();
|
||||
$series = NestedArray::getValue($user_inputs, $element_parents);
|
||||
$separator = $series['import']['csv_separator'];
|
||||
while ($row = fgetcsv($handle, 0, $separator)) {
|
||||
foreach ($row as $column_value) {
|
||||
$element_state['data_collector_table'][$rows_count][] = [
|
||||
'data' => self::convertEncoding($column_value, $encoding),
|
||||
];
|
||||
}
|
||||
$rows_count++;
|
||||
}
|
||||
fclose($handle);
|
||||
|
||||
\Drupal::messenger()->addMessage(t('Successfully imported @file', [
|
||||
'@file' => $file_upload->getClientOriginalName(),
|
||||
]));
|
||||
|
||||
// Updating form state storage.
|
||||
self::setElementState($element_parents, $form_state, $element_state);
|
||||
// Making sure that the user input is updated as well.
|
||||
$input = $form_state->getUserInput();
|
||||
NestedArray::setValue($input, $element_parents, $element_state);
|
||||
$form_state->setUserInput($input);
|
||||
}
|
||||
else {
|
||||
\Drupal::messenger()
|
||||
->addError(t('There was a problem importing the provided file data.'));
|
||||
}
|
||||
|
||||
$form_state->setRebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to build a button render array for the various data table.
|
||||
*
|
||||
* Operation.
|
||||
*/
|
||||
private static function buildOperationButton($operation, $on, $id_prefix, $wrapper_id, $index = NULL, $attributes = [], $wrapper_attributes = []) {
|
||||
$name = $id_prefix . '_' . $operation . '_' . $on;
|
||||
$submit = [];
|
||||
|
||||
if (!is_null($index)) {
|
||||
$name .= '_' . $index;
|
||||
$submit['#' . $on . '_index'] = $index;
|
||||
}
|
||||
|
||||
if ($attributes) {
|
||||
$submit['#attributes'] = $attributes;
|
||||
}
|
||||
|
||||
if ($wrapper_attributes) {
|
||||
$submit['#wrapper_attributes'] = $wrapper_attributes;
|
||||
}
|
||||
|
||||
$value = [];
|
||||
$value['add']['row'] = t('Add row');
|
||||
$value['add']['column'] = t('Add column');
|
||||
$value['delete']['row'] = t('Delete row');
|
||||
$value['delete']['column'] = t('Delete column');
|
||||
|
||||
$submit += [
|
||||
'#type' => 'submit',
|
||||
'#name' => $name,
|
||||
'#value' => $value[$operation][$on],
|
||||
'#limit_validation_errors' => [],
|
||||
'#submit' => [[get_called_class(), 'tableOperationSubmit']],
|
||||
'#operation' => $operation,
|
||||
'#operation_on' => $on,
|
||||
'#ajax' => [
|
||||
'callback' => [get_called_class(), 'ajaxRefresh'],
|
||||
'wrapper' => $wrapper_id,
|
||||
'effect' => 'fade',
|
||||
],
|
||||
];
|
||||
|
||||
return $submit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes an empty table.
|
||||
*
|
||||
* @param array $element
|
||||
* The element.
|
||||
* @param string $identifier_value
|
||||
* The identifier value.
|
||||
*
|
||||
* @return array
|
||||
* The element state storage.
|
||||
*/
|
||||
private static function initializeEmptyTable(array $element, string $identifier_value) {
|
||||
$is_first_column = $identifier_value === self::FIRST_COLUMN;
|
||||
$columns = $element['#initial_columns'];
|
||||
$columns_arr = range(0, $columns - 1);
|
||||
$rows = $element['#initial_rows'];
|
||||
$rows_arr = range(0, $rows - 1);
|
||||
|
||||
$data = [];
|
||||
$first_row_key = NULL;
|
||||
$counter_default_used_color_index = 0;
|
||||
$max_default_colors = count($element['#default_colors']);
|
||||
foreach ($rows_arr as $i) {
|
||||
$first_row_key = $first_row_key ?? $i;
|
||||
$table_first_row = $i === $first_row_key;
|
||||
$first_col_key = NULL;
|
||||
foreach ($columns_arr as $j) {
|
||||
$first_col_key = $first_col_key ?? $j;
|
||||
$table_first_col = $j === $first_col_key;
|
||||
// Used to skip category cell.
|
||||
$is_category_cell = $table_first_col && $table_first_row;
|
||||
$data[$i][$j]['data'] = '';
|
||||
if (!$is_category_cell && (($is_first_column && $i === $first_row_key) || (!$is_first_column && $j === $first_col_key))) {
|
||||
if ($counter_default_used_color_index === $max_default_colors) {
|
||||
$counter_default_used_color_index = 0;
|
||||
}
|
||||
$data[$i][$j]['color'] = $element['#default_colors'][$counter_default_used_color_index] ?? self::randomColor();
|
||||
$counter_default_used_color_index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs add or delete operation on the table row.
|
||||
*
|
||||
* @param array $element_state
|
||||
* The element state storage.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state.
|
||||
* @param string $op
|
||||
* The operation.
|
||||
* @param null|int $index
|
||||
* The row index.
|
||||
*
|
||||
* @return array
|
||||
* The updated element state storage.
|
||||
*/
|
||||
private static function tableRowOperation(array $element_state, FormStateInterface $form_state, $op, $index = NULL) {
|
||||
if ($op === 'delete') {
|
||||
// When only one row left we just empty it's columns.
|
||||
if (count($element_state['data_collector_table']) === 1) {
|
||||
$row = $element_state['data_collector_table'][$index];
|
||||
$element_state['data_collector_table'][$index][] = self::emptyRowColumns($row);
|
||||
return $element_state;
|
||||
}
|
||||
unset($element_state['data_collector_table'][$index]);
|
||||
}
|
||||
else {
|
||||
$first_row = current($element_state['data_collector_table']);
|
||||
$element_state['data_collector_table'][] = self::emptyRowColumns($first_row);
|
||||
}
|
||||
|
||||
return $element_state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs add or delete operation on the table column.
|
||||
*
|
||||
* @param array $element_state
|
||||
* The element state storage.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state.
|
||||
* @param string $op
|
||||
* The operation.
|
||||
* @param array $element_parents
|
||||
* The element parents.
|
||||
* @param null|int $index
|
||||
* The column index.
|
||||
*
|
||||
* @return array
|
||||
* The updated element state storage.
|
||||
*/
|
||||
private static function tableColumnOperation(array $element_state, FormStateInterface $form_state, $op, array $element_parents, $index = NULL) {
|
||||
if ($op === 'delete') {
|
||||
foreach ($element_state['data_collector_table'] as $row_key => $columns) {
|
||||
$row = $element_state['data_collector_table'][$row_key];
|
||||
if (count(self::excludeWeightColumnFromRow($row)) === 1) {
|
||||
$element_state['data_collector_table'][$row_key][$index]['data'] = '';
|
||||
}
|
||||
else {
|
||||
array_splice($element_state['data_collector_table'][$row_key], $index, 1);
|
||||
|
||||
// Making sure that the user input is updated as well.
|
||||
$user_input = $form_state->getUserInput();
|
||||
$values = NestedArray::getValue($form_state->getUserInput(), $element_parents);
|
||||
if (!empty($values['data_collector_table'][$row_key][$index])) {
|
||||
array_splice($values['data_collector_table'][$row_key], $index, 1);
|
||||
}
|
||||
NestedArray::setValue($user_input, $element_parents, $values);
|
||||
$form_state->setUserInput($user_input);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
foreach ($element_state['data_collector_table'] as $row_key => $columns) {
|
||||
$element_state['data_collector_table'][$row_key][]['data'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
return $element_state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Excludes weight column from row.
|
||||
*
|
||||
* @param array $row
|
||||
* The row.
|
||||
*
|
||||
* @return array
|
||||
* The columns.
|
||||
*/
|
||||
private static function excludeWeightColumnFromRow(array $row) {
|
||||
return array_filter(array_keys($row), function ($key) {
|
||||
return is_int($key);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty row columns.
|
||||
*
|
||||
* @param array $row
|
||||
* The row.
|
||||
*
|
||||
* @return array
|
||||
* The empty row column.
|
||||
*/
|
||||
private static function emptyRowColumns(array $row) {
|
||||
$columns = self::excludeWeightColumnFromRow($row);
|
||||
$empty_row_columns = [];
|
||||
foreach ($columns as $key => $column) {
|
||||
$empty_row_columns[$key]['data'] = '';
|
||||
}
|
||||
return $empty_row_columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to detect and convert strings not in UTF-8 to UTF-8.
|
||||
*
|
||||
* @param string $data
|
||||
* The string which needs converting.
|
||||
* @param string $encoding
|
||||
* The encoding of the CSV file.
|
||||
*
|
||||
* @return string
|
||||
* UTF encoded string.
|
||||
*/
|
||||
private static function convertEncoding($data, $encoding) {
|
||||
// Converting UTF-8 to UTF-8 will not work.
|
||||
if ($encoding == 'UTF-8') {
|
||||
return $data;
|
||||
}
|
||||
|
||||
// Try to convert the data to UTF-8.
|
||||
if ($encoded_data = Unicode::convertToUtf8($data, $encoding)) {
|
||||
return $encoded_data;
|
||||
}
|
||||
|
||||
// Fallback on the input data.
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the categories from the data collected by this element.
|
||||
*
|
||||
* @param array $data
|
||||
* The data.
|
||||
* @param string $type
|
||||
* The chart type.
|
||||
*
|
||||
* @return array
|
||||
* The category label and data.
|
||||
*/
|
||||
public static function getCategoriesFromCollectedTable(array $data, string $type) {
|
||||
$categories_identifier = $data['table_categories_identifier'] ?? '';
|
||||
$table = $data['data_collector_table'];
|
||||
$categories = [];
|
||||
|
||||
$is_first_column = $categories_identifier === self::FIRST_COLUMN;
|
||||
$first_row = current($table);
|
||||
$category_col_key = key($first_row);
|
||||
$categories['label'] = $first_row[$category_col_key];
|
||||
$data = [];
|
||||
if ($is_first_column) {
|
||||
if (!in_array($type, ['pie', 'donut'])) {
|
||||
// Extracting the categories data.
|
||||
$col_cells = array_column($table, $category_col_key);
|
||||
foreach ($col_cells as $cell) {
|
||||
$data[] = is_array($cell) ? $cell['data'] : $cell;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$col_cells = array_values($first_row);
|
||||
foreach ($col_cells as $cell) {
|
||||
$data[] = is_array($cell) ? $cell['data'] : $cell;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$col_cells = array_values($first_row);
|
||||
foreach ($col_cells as $cell) {
|
||||
$data[] = is_array($cell) ? $cell['data'] : $cell;
|
||||
}
|
||||
}
|
||||
$categories['data'] = $data;
|
||||
// Removing the category label from categories.
|
||||
$categories_data = $categories['data'];
|
||||
array_shift($categories_data);
|
||||
|
||||
$categories['data'] = $categories_data;
|
||||
|
||||
return $categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the series from the data collected by this element.
|
||||
*
|
||||
* @param array $data
|
||||
* The data.
|
||||
* @param string $type
|
||||
* The type of chart.
|
||||
*
|
||||
* @return array
|
||||
* The series.
|
||||
*/
|
||||
public static function getSeriesFromCollectedTable(array $data, string $type) {
|
||||
$table = $data['data_collector_table'];
|
||||
$categories_identifier = $data['table_categories_identifier'] ?? '';
|
||||
|
||||
/** @var \Drupal\charts\TypeManager $chart_type_plugin_manager */
|
||||
$chart_type_plugin_manager = \Drupal::service('plugin.manager.charts_type');
|
||||
$chart_type = $chart_type_plugin_manager->getDefinition($type);
|
||||
$is_single_axis = $chart_type['axis'] === ChartInterface::SINGLE_AXIS;
|
||||
|
||||
$is_first_column = $categories_identifier === self::FIRST_COLUMN;
|
||||
$first_row = current($table);
|
||||
$category_col_key = key($first_row);
|
||||
|
||||
// Skip the first row if it's considered as the holding categories data.
|
||||
if (!$is_first_column) {
|
||||
array_shift($table);
|
||||
}
|
||||
|
||||
$series = [];
|
||||
$i = 0;
|
||||
foreach ($table as $row) {
|
||||
if (!$is_first_column) {
|
||||
$name_key = key($row);
|
||||
$series[$i]['name'] = $row[$name_key]['data'] ?? [];
|
||||
$series[$i]['color'] = $row[$name_key]['color'] ?? '';
|
||||
// Removing the name from data array.
|
||||
unset($row[$name_key]);
|
||||
foreach ($row as $column) {
|
||||
// Get all the data in this column and break out of this loop.
|
||||
if ($is_single_axis) {
|
||||
if (is_numeric($column) || is_string($column)) {
|
||||
$series[$i]['data'][] = [
|
||||
$series[$i]['name'],
|
||||
self::castValueToNumeric($column),
|
||||
];
|
||||
}
|
||||
elseif (is_array($column) && isset($column['data'])) {
|
||||
$series[$i]['data'][] = [
|
||||
$series[$i]['name'],
|
||||
self::castValueToNumeric($column['data']),
|
||||
];
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (is_numeric($column) || is_string($column)) {
|
||||
$series[$i]['data'][] = self::castValueToNumeric($column);
|
||||
}
|
||||
elseif (is_array($column) && isset($column['data'])) {
|
||||
$series[$i]['data'][] = self::castValueToNumeric($column['data']);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Adding a couple types not currently supported but hopefully soon.
|
||||
if (in_array($type, [
|
||||
'scatter',
|
||||
'bubble',
|
||||
'candlestick',
|
||||
'boxplot',
|
||||
])) {
|
||||
// Enclose the data value in an array.
|
||||
$series[$i]['data'] = [$series[$i]['data']];
|
||||
}
|
||||
}
|
||||
else {
|
||||
$j = 0;
|
||||
foreach ($row as $column_key => $column) {
|
||||
// Skipping the category label and it's data.
|
||||
if ($column_key === $category_col_key || !is_numeric($column_key)) {
|
||||
continue;
|
||||
}
|
||||
elseif ($i === 0) {
|
||||
// This is the first column which holds the data names and colors.
|
||||
$series[$j]['name'] = $column['data'] ?? $column;
|
||||
$series[$j]['color'] = $column['color'] ?? self::randomColor();
|
||||
}
|
||||
else {
|
||||
// Get all the data in this column and break out of this loop.
|
||||
$cell_value = is_array($column) && isset($column['data']) ? $column['data'] : $column;
|
||||
$cell_value = self::castValueToNumeric($cell_value);
|
||||
if ($is_single_axis) {
|
||||
$series[$j]['data'][] = [$series[$j]['name'], $cell_value];
|
||||
$series[$j]['title'][] = $row[0]['data'];
|
||||
}
|
||||
elseif (in_array($type, [
|
||||
'scatter',
|
||||
'bubble',
|
||||
'candlestick',
|
||||
'boxplot',
|
||||
])) {
|
||||
$series[$j]['data'][0][] = $cell_value;
|
||||
}
|
||||
else {
|
||||
$series[$j]['data'][] = $cell_value;
|
||||
}
|
||||
}
|
||||
$j++;
|
||||
}
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
|
||||
return $series;
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts string value to numeric.
|
||||
*
|
||||
* @param string $value
|
||||
* The value.
|
||||
*
|
||||
* @return float|int
|
||||
* The numeric value.
|
||||
*/
|
||||
private static function castValueToNumeric($value) {
|
||||
if (is_numeric($value)) {
|
||||
$value = is_int($value) ? (integer) $value : (float) $value;
|
||||
}
|
||||
elseif ($value === '') {
|
||||
$value = NULL;
|
||||
}
|
||||
else {
|
||||
$value = 0;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user