GithubHelp home page GithubHelp logo

redchamps / clean-admin-menu Goto Github PK

View Code? Open in Web Editor NEW
120.0 5.0 9.0 67 KB

Magento 2 Extension to cleanup admin menu and Store > Configuration area by arranging third party extension items.

Home Page: https://redchamps.com

License: MIT License

PHP 71.17% HTML 16.27% JavaScript 1.49% Less 11.07%
magento2 magento2-extension magento2-extension-free magento2-module

clean-admin-menu's Introduction

Clean Admin Menu - Magento 2 Extension

Latest Stable Version License: MIT Packagist Packagist

It will merge all 3rd party extension's menu items in backend's primary menu to a common menu item named "Extensions". It will also merge different 3rd party extension tabs under path

Store > Configuration

to a single tab named "Extensions" and place it after native Magento tabs.

Screenshots

Main Navigation

Primary Navigation

Extensions configuration

System Config

Comparision - admin before this extension and after

Before and after comparision

Installation

composer require redchamps/module-clean-admin-menu

Configuration

Various settings to tweak the extension behaviour are located under admin path

Store > Configuration > Extensions > RedChamps > Clean Admin Menu

Troubleshooting

If any extension menu item is not moved under "Extensions" menu then just find its menu id and enter it in setting

Store > Configuration > Extensions > RedChamps > Clean Admin Menu > Developer Tools > Move Menu ID's

Authors

  • RedChamps [Maintainer] Twitter Follow

  • Ravinder [Maintainer] Twitter Follow

  • Thomas Klein [Contributor] Twitter Follow

  • Prince Antil [Contributor] Twitter Follow

License

This project is licensed under the MIT License - see the LICENSE details.

ADS

Please visit our store for more free/paid extensions from us.

clean-admin-menu's People

Contributors

princeantil90 avatar rav-redchamps avatar thomas-kl1 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

clean-admin-menu's Issues

Notice: Undefined index

Installed module on M2.3.3 via composer, went to admin and received error in admin. s:up, c:c, c:f did not resolve the issue

1 exception(s):
Exception #0 (Exception): Notice: Undefined index: module in vendor/redchamps/module-clean-admin-menu/Plugin/Model/MenuBuilderCommand.php on line 11

Exception #0 (Exception): Notice: Undefined index: module in vendor/redchamps/module-clean-admin-menu/Plugin/Model/MenuBuilderCommand.php on line 11
<pre>#1 RedChamps\CleanMenu\Plugin\Model\MenuBuilderCommand->afterExecute() called at [vendor/magento/framework/Interception/Interceptor.php:146]
#2 Magento\Backend\Model\Menu\Builder\Command\Remove\Interceptor->Magento\Framework\Interception\{closure}() called at [vendor/magento/framework/Interception/Interceptor.php:153]
#3 Magento\Backend\Model\Menu\Builder\Command\Remove\Interceptor->___callPlugins() called at [generated/code/Magento/Backend/Model/Menu/Builder/Command/Remove/Interceptor.php:52]
#4 Magento\Backend\Model\Menu\Builder\Command\Remove\Interceptor->execute() called at [vendor/magento/module-backend/Model/Menu/Builder.php:65]
#5 Magento\Backend\Model\Menu\Builder->getResult() called at [vendor/magento/framework/Interception/Interceptor.php:58]
#6 Magento\Backend\Model\Menu\Builder\Interceptor->___callParent() called at [vendor/magento/framework/Interception/Interceptor.php:138]
#7 Magento\Backend\Model\Menu\Builder\Interceptor->Magento\Framework\Interception\{closure}() called at [vendor/magento/framework/Interception/Interceptor.php:153]
#8 Magento\Backend\Model\Menu\Builder\Interceptor->___callPlugins() called at [generated/code/Magento/Backend/Model/Menu/Builder/Interceptor.php:39]
#9 Magento\Backend\Model\Menu\Builder\Interceptor->getResult() called at [vendor/magento/module-backend/Model/Menu/Config.php:148]
#10 Magento\Backend\Model\Menu\Config->_initMenu() called at [vendor/magento/module-backend/Model/Menu/Config.php:111]
#11 Magento\Backend\Model\Menu\Config->getMenu() called at [vendor/magento/module-backend/Block/Menu.php:265]
#12 Magento\Backend\Block\Menu->getMenuModel() called at [generated/code/Magento/Backend/Block/Menu/Interceptor.php:50]
#13 Magento\Backend\Block\Menu\Interceptor->getMenuModel() called at [vendor/magento/module-backend/Model/View/Result/Page.php:28]
#14 Magento\Backend\Model\View\Result\Page->setActiveMenu() called at [generated/code/Magento/Backend/Model/View/Result/Page/Interceptor.php:24]
#15 Magento\Backend\Model\View\Result\Page\Interceptor->setActiveMenu() called at [vendor/magento/module-catalog/Controller/Adminhtml/Product/Edit.php:71]
#16 Magento\Catalog\Controller\Adminhtml\Product\Edit->execute() called at [generated/code/Magento/Catalog/Controller/Adminhtml/Product/Edit/Interceptor.php:24]
#17 Magento\Catalog\Controller\Adminhtml\Product\Edit\Interceptor->execute() called at [vendor/magento/framework/App/Action/Action.php:108]
#18 Magento\Framework\App\Action\Action->dispatch() called at [vendor/magento/module-backend/App/AbstractAction.php:231]
#19 Magento\Backend\App\AbstractAction->dispatch() called at [vendor/magento/framework/Interception/Interceptor.php:58]
#20 Magento\Catalog\Controller\Adminhtml\Product\Edit\Interceptor->___callParent() called at [vendor/magento/framework/Interception/Interceptor.php:138]
#21 Magento\Catalog\Controller\Adminhtml\Product\Edit\Interceptor->Magento\Framework\Interception\{closure}() called at [vendor/magento/module-backend/App/Action/Plugin/Authentication.php:143]
#22 Magento\Backend\App\Action\Plugin\Authentication->aroundDispatch() called at [vendor/magento/framework/Interception/Interceptor.php:135]
#23 Magento\Catalog\Controller\Adminhtml\Product\Edit\Interceptor->Magento\Framework\Interception\{closure}() called at [vendor/magento/framework/Interception/Interceptor.php:153]
#24 Magento\Catalog\Controller\Adminhtml\Product\Edit\Interceptor->___callPlugins() called at [generated/code/Magento/Catalog/Controller/Adminhtml/Product/Edit/Interceptor.php:39]
#25 Magento\Catalog\Controller\Adminhtml\Product\Edit\Interceptor->dispatch() called at [vendor/magento/framework/App/FrontController.php:159]
#26 Magento\Framework\App\FrontController->processRequest() called at [vendor/magento/framework/App/FrontController.php:99]
#27 Magento\Framework\App\FrontController->dispatch() called at [vendor/magento/framework/Interception/Interceptor.php:58]
#28 Magento\Framework\App\FrontController\Interceptor->___callParent() called at [vendor/magento/framework/Interception/Interceptor.php:138]
#29 Magento\Framework\App\FrontController\Interceptor->Magento\Framework\Interception\{closure}() called at [vendor/magento/framework/Interception/Interceptor.php:153]
#30 Magento\Framework\App\FrontController\Interceptor->___callPlugins() called at [generated/code/Magento/Framework/App/FrontController/Interceptor.php:26]
#31 Magento\Framework\App\FrontController\Interceptor->dispatch() called at [vendor/magento/framework/App/Http.php:137]
#32 Magento\Framework\App\Http->launch() called at [generated/code/Magento/Framework/App/Http/Interceptor.php:24]
#33 Magento\Framework\App\Http\Interceptor->launch() called at [vendor/magento/framework/App/Bootstrap.php:261]
#34 Magento\Framework\App\Bootstrap->run() called at [pub/index.php:40]
</pre>

No parent items results in an empty menu item

If the admin main menu does not have any items to clean up, the Extensions menu item still shows in the menu and does not open.

It would be nice if the Extensions item did not show when there are no parent items to group, while still cleaning up the Configuration menu items.

Multiple admin user Environment with restricted roles - Extensions menu invisible

Steps to repeat:

  1. Create a admin user with full admin access role (USER1) & another user having access only to sales & System > Cache section(USER2)
  2. Login with USER2, you will notice that Extensions menu is visible with no child items, now go to System > Cache Management & clear cache
  3. Extensions menu will disappear, which is correct as USER2 doesn't have access to it.
  4. Now login with USER1, you will notice that Extensions menu isn't visible until you clear Magento cache

Cause: Extensions menu get cached & doesn't appear/disappear based on admin user's ACL role

Exception #0 (Exception): Notice: Undefined index: in vendor/redchamps/module-clean-admin-menu/Model/RuleFactory.php on line 35

Environment: Magento Open Source 2.3.5-p2
PHP 7.3.28
MAGE_MODE: developer

Steps to reproduce:

  • install the extension via composer package manager
  • run the bin/magento setup:upgrade command
  • Open Magento Admin and observe the result

Actual result:

  • An error occurred on the page
    Exception #0 (Exception): Notice: Undefined index: in vendor/redchamps/module-clean-admin-menu/Model/RuleFactory.php on line 35

Expected result:

  • Magento Admin has been successfully loaded without errors.

Compatibility with ElasticSuite

There seems to be a compatibility issue with ElasticSuite menu items.

  1. Please install https://github.com/Smile-SA/elasticsuite
  2. Go to Extensions > ElasticSuite > Search Engine > Search Relevance
  3. Menu item will redirect to dashboard with Invalid security or form key. Please refresh the page.
  4. The route is smile_elasticsuite/search_request_relevanceconfig/index/

2.4.4 (PHP 8.1) compatibility

Extension will produce below error in the 2.4.4 (PHP 8.1) admin panel

Error: Call to undefined method ReflectionUnionType::getName() in /m2/244/vendor/magento/framework/Code/Generator/EntityAbstract.php:335

This is being caused by Magento 2.4.4 core issue reported here magento/magento2#35292 and we are looking for some temporary fix till the time the core issue gets resolved.

Notice: Undefined index: in vendor/redchamps/module-clean-admin-menu/Model/RuleFactory.php on line 35

When I install the module using I get an exception. Using magento 2.3.7-p1

composer require redchamps/module-clean-admin-menu
bin/magento set:up

Installed version is 1.1.3

1 exception(s):
Exception #0 (Exception): Notice: Undefined index:  in /data/a4h/magento2/vendor/redchamps/module-clean-admin-menu/Model/RuleFactory.php on line 35

Exception #0 (Exception): Notice: Undefined index:  in /data/a4h/magento2/vendor/redchamps/module-clean-admin-menu/Model/RuleFactory.php on line 35
<pre>#1 RedChamps\CleanMenu\Model\RuleFactory->create() called at [vendor/redchamps/module-clean-admin-menu/Model/IsAllowedStrategy.php:46]
#2 RedChamps\CleanMenu\Model\IsAllowedStrategy->isAllowed() called at [vendor/redchamps/module-clean-admin-menu/Model/Config/Structure/Data.php:43]
#3 RedChamps\CleanMenu\Model\Config\Structure\Data->get() called at [generated/code/RedChamps/CleanMenu/Model/Config/Structure/Data/Interceptor.php:24]
#4 RedChamps\CleanMenu\Model\Config\Structure\Data\Interceptor->get() called at [vendor/magento/module-config/Model/Config/Structure.php:127]
#5 Magento\Config\Model\Config\Structure->__construct() called at [generated/code/Magento/Config/Model/Config/Structure/Interceptor.php:14]
#6 Magento\Config\Model\Config\Structure\Interceptor->__construct() called at [vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php:121]
#7 Magento\Framework\ObjectManager\Factory\AbstractFactory->createObject() called at [vendor/magento/framework/ObjectManager/Factory/Dynamic/Developer.php:66]
#8 Magento\Framework\ObjectManager\Factory\Dynamic\Developer->create() called at [vendor/magento/framework/ObjectManager/ObjectManager.php:70]
#9 Magento\Framework\ObjectManager\ObjectManager->get() called at [vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php:167]
#10 Magento\Framework\ObjectManager\Factory\AbstractFactory->resolveArgument() called at [vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php:273]
#11 Magento\Framework\ObjectManager\Factory\AbstractFactory->getResolvedArgument() called at [vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php:236]
#12 Magento\Framework\ObjectManager\Factory\AbstractFactory->resolveArgumentsInRuntime() called at [vendor/magento/framework/ObjectManager/Factory/Dynamic/Developer.php:34]
#13 Magento\Framework\ObjectManager\Factory\Dynamic\Developer->_resolveArguments() called at [vendor/magento/framework/ObjectManager/Factory/Dynamic/Developer.php:59]
#14 Magento\Framework\ObjectManager\Factory\Dynamic\Developer->create() called at [vendor/magento/framework/ObjectManager/ObjectManager.php:56]
#15 Magento\Framework\ObjectManager\ObjectManager->create() called at [vendor/magento/framework/App/ActionFactory.php:44]
#16 Magento\Framework\App\ActionFactory->create() called at [vendor/magento/framework/App/Router/Base.php:306]
#17 Magento\Framework\App\Router\Base->matchAction() called at [vendor/magento/framework/App/Router/Base.php:167]
#18 Magento\Framework\App\Router\Base->match() called at [vendor/magento/framework/App/FrontController.php:115]
#19 Magento\Framework\App\FrontController->dispatch() called at [vendor/magento/framework/Interception/Interceptor.php:58]
#20 Magento\Framework\App\FrontController\Interceptor->___callParent() called at [vendor/magento/framework/Interception/Interceptor.php:138]
#21 Magento\Framework\App\FrontController\Interceptor->Magento\Framework\Interception\{closure}() called at [vendor/magento/framework/Interception/Interceptor.php:153]
#22 Magento\Framework\App\FrontController\Interceptor->___callPlugins() called at [generated/code/Magento/Framework/App/FrontController/Interceptor.php:26]
#23 Magento\Framework\App\FrontController\Interceptor->dispatch() called at [vendor/magento/framework/App/Http.php:116]
#24 Magento\Framework\App\Http->launch() called at [vendor/magento/framework/Interception/Interceptor.php:58]
#25 Magento\Framework\App\Http\Interceptor->___callParent() called at [vendor/magento/framework/Interception/Interceptor.php:138]
#26 Magento\Framework\App\Http\Interceptor->Magento\Framework\Interception\{closure}() called at [vendor/msp/adminrestriction/Plugin/AppInterfacePlugin.php:134]
#27 MSP\AdminRestriction\Plugin\AppInterfacePlugin->aroundLaunch() called at [vendor/magento/framework/Interception/Interceptor.php:135]
#28 Magento\Framework\App\Http\Interceptor->Magento\Framework\Interception\{closure}() called at [vendor/justbetter/magento2-sentry/Plugin/GlobalExceptionCatcher.php:43]
#29 JustBetter\Sentry\Plugin\GlobalExceptionCatcher->aroundLaunch() called at [vendor/magento/framework/Interception/Interceptor.php:135]
#30 Magento\Framework\App\Http\Interceptor->Magento\Framework\Interception\{closure}() called at [vendor/magento/framework/Interception/Interceptor.php:153]
#31 Magento\Framework\App\Http\Interceptor->___callPlugins() called at [generated/code/Magento/Framework/App/Http/Interceptor.php:26]
#32 Magento\Framework\App\Http\Interceptor->launch() called at [vendor/magento/framework/App/Bootstrap.php:261]
#33 Magento\Framework\App\Bootstrap->run() called at [pub/index.php:40]
</pre>

get rid of "version" in composer.json

As we are using VCS for this package and tags, the "version" argument is not needed in the composer.json.
Removing it will be make easier to ship new releases without having to bump the version in composer.json each time.

Extension not working with FME Extension menu

Issue Description:
The plugin is intended to work with different modules, including the FME extension. However, there is an issue where the FME extension menu item is not correctly setting in the Extension menu. Instead, it still has its own menu item, as shown in the attached screenshot.

Steps to Reproduce:

Install the plugin and the FME extension.
Navigate to the Extension menu in the admin panel.
Expected Behavior:
The FME extension menu item should be integrated into the Extension menu, providing a unified menu structure for all modules.

Actual Behavior:
The FME extension menu item remains separate from the Extension menu, causing a disjointed menu structure.

Screenshot:
Screen Shot 2024-02-20 at 7 01 24 PM

Accessing extensions requires new permissions after install

Any users set up with limited privileges who had been granted access to certain third-party extensions will initially no longer be able to access those once this module is installed, because they need to be granted access to the "Extensions" ACL resource that this module adds.

Ideally this would not be necessary, with the module's normal permissions taking effect without change, but at a minimum this should be documented in the setup instructions.

Can't uninstall this module. Help please.

I need to uninstall this module.
I've tried bin/magento module:uninstall --clear-static-content --remove-data as well as bin/magento module:disable RedChamps_CleanMenu followed by composer remove redchamps/module-clean-admin-menu.
Both commands successfully execute and after running setup:upgrade and clearing cache, I'm left with the following error when I try to load any page on the website:

1 exception(s): Exception #0 (Magento\Framework\Exception\FileSystemException): The contents from the "/var/www/html/vendor/redchamps/module-clean-admin-menu/etc/module.xml" file can't be read. Warning!file_get_contents(/var/www/html/vendor/redchamps/module-clean-admin-menu/etc/module.xml): failed to open stream: No such file or directory Exception #0 (Magento\Framework\Exception\FileSystemException): The contents from the "/var/www/html/vendor/redchamps/module-clean-admin-menu/etc/module.xml" file can't be read. Warning!file_get_contents(/var/www/html/vendor/redchamps/module-clean-admin-menu/etc/module.xml): failed to open stream: No such file or directory #1 Magento\Framework\Module\ModuleList\Loader->getModuleConfigs() called at [vendor/magento/framework/Module/ModuleList/Loader.php:83] #2 Magento\Framework\Module\ModuleList\Loader->load() called at [vendor/magento/framework/Module/ModuleList.php:72] #3 Magento\Framework\Module\ModuleList->getAll() called at [vendor/magento/framework/Module/ModuleList.php:91] #4 Magento\Framework\Module\ModuleList->getOne() called at [vendor/magento/framework/Module/DbVersionInfo.php:144] #5 Magento\Framework\Module\DbVersionInfo->isModuleVersionEqual() called at [vendor/magento/framework/Module/DbVersionInfo.php:59] #6 Magento\Framework\Module\DbVersionInfo->isSchemaUpToDate() called at [vendor/magento/framework/Module/DbVersionInfo.php:103] #7 Magento\Framework\Module\DbVersionInfo->getDbVersionErrors() called at [vendor/magento/framework/Module/Plugin/DbStatusValidator.php:119] #8 Magento\Framework\Module\Plugin\DbStatusValidator->getGroupedDbVersionErrors() called at [vendor/magento/framework/Module/Plugin/DbStatusValidator.php:53] #9 Magento\Framework\Module\Plugin\DbStatusValidator->beforeDispatch() called at [vendor/magento/framework/Interception/Interceptor.php:121] #10 Magento\Framework\App\FrontController\Interceptor->Magento\Framework\Interception\{closure}() called at [vendor/amasty/aminvisiblecaptcha/Plugin/Framework/App/FrontControllerInterface/ValidateCaptcha.php:118] #11 Amasty\InvisibleCaptcha\Plugin\Framework\App\FrontControllerInterface\ValidateCaptcha->aroundDispatch() called at [vendor/magento/framework/Interception/Interceptor.php:135] #12 Magento\Framework\App\FrontController\Interceptor->Magento\Framework\Interception\{closure}() called at [vendor/magento/module-page-cache/Model/App/FrontController/BuiltinPlugin.php:75] #13 Magento\PageCache\Model\App\FrontController\BuiltinPlugin->aroundDispatch() called at [vendor/magento/framework/Interception/Interceptor.php:135] #14 Magento\Framework\App\FrontController\Interceptor->Magento\Framework\Interception\{closure}() called at [vendor/magento/framework/Interception/Interceptor.php:153] #15 Magento\Framework\App\FrontController\Interceptor->___callPlugins() called at [generated/code/Magento/Framework/App/FrontController/Interceptor.php:23] #16 Magento\Framework\App\FrontController\Interceptor->dispatch() called at [vendor/magento/framework/App/Http.php:116] #17 Magento\Framework\App\Http->launch() called at [generated/code/Magento/Framework/App/Http/Interceptor.php:23] #18 Magento\Framework\App\Http\Interceptor->launch() called at [vendor/magento/framework/App/Bootstrap.php:264] #19 Magento\Framework\App\Bootstrap->run() called at [pub/index.php:29]

Please help me. I don't understand why Magento is still looking for this module....
I double checked the setup_module table, composer.json, and composer.lock files, and the module isn't mentioned anywhere in them.

Thanks in advance.

Sort alphabetically

A nice addition to this would be to alphabetically sort the third-party extensions with the "Extensions" tabs in Stores > Configuration.

Sub Menu feature

Hi,
I'm not a big fan of Amasty, but they make something nice to see to regroup menu.

menu

Actually for example with elastic suite menu, the subtitle are link to dashboard :
menu2

2.4 compatibility

Do you still maintain this? It would be great if you make this working with latest Magento release 2.4.3

config import fails

Hello,

getting the following error upon config import:

$ magento app:config:import
Processing configurations data from configuration file...
Import failed: Class RedChamps\CleanMenu\Model\Config\Backend\Modules does not exist

The class RedChamps\CleanMenu\Model\Config\Backend\Modules really doesn't exist, but why does it ask for it?

Any idea?

Submenu is not rendering correctly

I noticed in two different installations that the menu is not breaking up into submenus. See screenshot of the correct menu:
image
And on the other installation, it looks like this:
image
The difference in the html is that the first menu has level-0 class, where on the second screenshot, the same menu is level-1.
The first one is Magento 2.3.6, the second one is 2.4.2

"Powered by" labels

I would request that you reconsider the "powered by Redchamps" links that you have added to this module in a recent version. While I understand and appreciate that you're putting your time into maintaining this as an open source product, which you have no obligation to do, including these links directly violates the whole purpose of this module: which is to clean up the Magento admin. Yes, the links are less intrusive than vendors sticking their logos all over the place, but for us, it still undermines the reason we had begun installing this for clients (sufficiently so we may stop using it).

It is of course your right to leave these in, and I absolutely understand the motivation, but I do hope you will change your mind.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.