Coder Social home page Coder Social logo

itzamirreza / ezchestshop Goto Github PK

View Code? Open in Web Editor NEW
21.0 1.0 22.0 1.66 MB

Ez Chest Shop plugin source code available on spigotmc.org

License: MIT License

Java 100.00%
spigot spigot-plugin minecraft bukkit-plugin minecraft-plugin shop chest chestshop

ezchestshop's Introduction

first img

Discord Download Size Total Spigot Downloads Spigot Rating Current Version Servers

Welcome to EzChestShop! A Chest Shop plugin that's simple to use, filled with features and increadibly customizeable! Head over to spigotmc.org to try the compiled version of EzChestShop for yourself!

Configuration

If you want to customize EzChestShop, check out our Wiki, we have a section about the config.yml!

Found a bug, miss a feature?

Create an Issue or let us know on Discord! We're always happy to help you out if you experience any troubles, though maybe you'll already find your answer in our #faq on Discord!

Contributing:

Want a feature implemented quickly? The fastest way is by creating the feature yourself via a Pull Request! See the Wiki for more Infos on how to contribute!

Ignore this part:

2023-03-04_20 35 02 2021-09-03_01 23 00 2021-09-03_12 35 36 intro INFINITE FEATURES 2021-09-03_00 25 24 2021-09-03_00 25 27 2021-09-03_00 25 32 2023-03-04_20 35 10

Content_creators installations

commands support image image

ezchestshop's People

Contributors

daretmavi avatar elitogame avatar hopeful-ly avatar itzamirreza avatar pfeiferocks avatar pizzathatcodes avatar thewylot avatar zeltuv avatar zyrandev avatar

Stargazers

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

Watchers

 avatar

ezchestshop's Issues

Error in the PlayerToggleSneakEvent

The plugin gives an error when you sneak while looking to a regular (non-shop) chest.
I think adding following code after the 241. line on the PlayerCloseToChestListener.java will fix the issue but I can't test.
if (!ShopContainer.isShop(loc)) return;

Console log of the error:

- [Mon 18:05:44 ERROR Minecraft] Could not pass event PlayerToggleSneakEvent to EzChestShop v1.5.4
java.lang.NullPointerException: Cannot invoke "String.getBytes(java.nio.charset.Charset)" because "src" is null
    at java.base/java.util.Base64$Decoder.decode(Base64.java:589)
    at EzChestShop-1.5.4.jar//me.deadlight.ezchestshop.Utils.Utils.decodeItem(Utils.java:118)
    at EzChestShop-1.5.4.jar//me.deadlight.ezchestshop.Listeners.PlayerCloseToChestListener.onPlayerSneak(PlayerCloseToChestListener.java:279)
    at com.destroystokyo.paper.event.executor.asm.generated.GeneratedEventExecutor671.execute(Unknown Source)
    at org.bukkit.plugin.EventExecutor$2.execute(EventExecutor.java:77)
    at co.aikar.timings.TimedEventExecutor.execute(TimedEventExecutor.java:80)
    at org.bukkit.plugin.RegisteredListener.callEvent(RegisteredListener.java:70)
    at io.papermc.paper.plugin.manager.PaperEventManager.callEvent(PaperEventManager.java:54)
    at io.papermc.paper.plugin.manager.PaperPluginManagerImpl.callEvent(PaperPluginManagerImpl.java:126)
    at org.bukkit.plugin.SimplePluginManager.callEvent(SimplePluginManager.java:615)
    at net.minecraft.server.network.ServerGamePacketListenerImpl.handlePlayerCommand(ServerGamePacketListenerImpl.java:2621)
    at net.minecraft.network.protocol.game.ServerboundPlayerCommandPacket.handle(ServerboundPlayerCommandPacket.java:37)
    at net.minecraft.network.protocol.game.ServerboundPlayerCommandPacket.handle(ServerboundPlayerCommandPacket.java:13)
    at net.minecraft.network.protocol.PacketUtils.lambda$ensureRunningOnSameThread$0(PacketUtils.java:51)
    at net.minecraft.server.TickTask.run(TickTask.java:18)
    at net.minecraft.util.thread.BlockableEventLoop.doRunTask(BlockableEventLoop.java:153)
    at net.minecraft.util.thread.ReentrantBlockableEventLoop.doRunTask(ReentrantBlockableEventLoop.java:24)
    at net.minecraft.server.MinecraftServer.doRunTask(MinecraftServer.java:1341)
    at net.minecraft.server.MinecraftServer.d(MinecraftServer.java:

Update Notify issues

currently using 1.5.6 but getting Update Notify with this title: 1.5.6 OMG MEGA UPDATE!

Taxes

Short Description

Implement a system for Servers to enforce taxes.

Implementation

  • Whenever a user buys a item, a percentage of the price paid will be taxed and not arrive in the shop owners bank account.
  • If a user sells a item, a percentage of the money he receives will be taxed.
  • Default Configuration:
    • Tax admin shops: false
    • Tax player shops: false
    • Tax rate player shop purchases: 20 (%) or 0.2 (Input type is up to you, however keep it uniform and consistent with the rest of the plugin if any percent settings are given already)
    • Tax rate player shop sale: 20%
    • Tax rate admin shop purchase: 20%
    • Tax rate admin shop sale: 20%
    • Under the permission category add a tax permission toggle. If true we will have the following permissions:
      • ecs.shops.taxes.playershop.sale.20
      • ecs.shops.taxes.playershop.purchase.20
      • ecs.shops.taxes.adminshop.sale.20
      • ecs.shops.taxes.adminshop.purchase.20

Item Blacklist

Short Description

Some servers might have unique items, they do not want to be sold. May it be due to a story quest or just because a dupe gave some person 9 million barrier blocks. In these cases or others, you might want to stop the trade of certain items with a blacklist.

Implementation

  • Create a new config file to handle the blacklist (itemblacklist.yml). Some servers might add a ton of items to this list. In that case, storing the blacklist in the standard config, would make it quite unreadable.
  • The way items are defined will vary a lot, so there should be a bunch of options:
blacklist:
  # -------- Compact design: ---------
  # The material can be defined via material:name. This should use the same names as F3+H ingame, not the bukkit STONE names.
  - material:stone 
  # Using two ;; separates options. itemname and lore can be used to filter for these settings. \n equals a new line of lore. 
  # If a lore or name line starts with &- it means that the check should ignore colors. ChatColor.stripcolors()
  - material:diamond_pickaxe;;itemname:King's Axe!;;itemlore:Only wielded by\n&-the king during combat!
  # Enchantments can be defined with or without a associated level. /{level} and are separated by comma. 
  # A > or < sign can be use limit to values greater then or less then.
  - enchantments:depth_strider,feather_falling/4,unbreaking/>1,mending
  # potions work the same way as Enchantments. Durability can be a value or if it ends with % a percent value. 
  # Can be used with > or < too.
  - potions:slow_falling,resistance/<4;;durability:100%
  # Attributes consist of the syntax: name of the attribute/ value of the attribute/ operation of the attribute/ affected slots.
  # Slots are separated by the & symbol.
  # Hide flags targets the various flags to hide. Unbreakable is a boolean value, either true or false.
  - attributes:max_health/1/amount/mainhand&offhand;;hide_flags:modifiers,unbreakable;;unbrekable:true
  # -------- Readable/Comfy design: ---------
  # Items have a identifier name to find them easier.
  - item1:
    material: fishing_rod
    enchantments:
      - lure: >1
      # Level 0 equals not here at all. Level -1 equals enchantment is present with any level.
      - unbreaking: 0
    unbreakable: true
  • Implement a ingame blacklist editor. /ecsadmin blacklist will show a gui list of all the blacklist filters. If a filter doesn't have a material, display a replacement item here instead (paper or structure void)
  • Filters don't neccessarily need a item. When inside the blacklist gui, have a button to select a item from your inventory. Whichever item you click next in your bottom inventory will be added as a blacklisted item. You will be directed to a new window with various buttons. You can select all the item settings you want to use for the blacklist:
[Material]   [Name]     [Lore]     ...
 enabled    disabled   disabled    ...
  • A shop selling a blacklisted item should still be opened when clicked, however, the buy and sell options should be disabled. Creating a shop with a blacklisted item should throw an error.

Abbreviating large numbers.

Short Description

Add an option to abbreviate larger numbers with letters like k for thousand, m for million, b for billion, etc.

Implementation

  • Add a a config.yml setting to enable/disable this feature for input and the for outputs individually. See the following list for default values:
    • any Input: true
    • Hologram Output: false
    • Shop Buy/Sell GUI Output: false
    • Checkprofits: false
  • Alternatively for output, the LanguageManager could be updated. Any Number type output that ends with _abbreviated or something similar will be displayed abbreviated. A config for input should still be required.
  • Input like 1,5k or 1.03K should be handled too.

Additional Notes:

This quick snippet might help you get going ;)

    public static String coolNumberFormat(long count) {
        if (count < 1000) return "" + count;
        int exp = (int) (Math.log(count) / Math.log(1000));
        DecimalFormat format = new DecimalFormat("0.#");
        String value = format.format(count / Math.pow(1000, exp));
        return String.format("%s%c", value, "kMBTPE".charAt(exp - 1));
    }

Debug mode logs

I'm seeing an error in my logs, but I can't find a debug mode to enable to try to find what's wrong with the shop. Please add a debug log level to the config so I can get more detail about what is going on surrounding the error message. I'm getting many of:

[22:20:47] [Server thread/INFO]: [ECS] Something unexpected happened with this container's data, so this shop has been removed.
[22:21:09] [Server thread/INFO]: [ECS] Something unexpected happened with this container's data, so this shop has been removed.

EzChestShop - version 1.5.0

Shop Creation Cost

Short Description

Whenever a user creates a shop charge them a fee.

Implementation

  • Charge a user x cash whenever he creates a shop.
  • Default Configuration options:
    • Shop creation cost enabled: false
    • Shop creation cost: 50 cash? If you think that's to little or too much change it.
    • Shop creation cost incremental enabled: false (determines if the price goes up when you have more active shops)
    • Shop creation cost incremental type: linear (linear [each shop costs x * value more] | exponential [Use this with the price being the initial value, the amount of shops being the elapsed time and the growth rate being the value]
    • Shop creation cost incremental value: 1 (if type == linear, this value will simply be added x times to the price. If type == exponential, the value is a percent growth)

ERROR: Could not pass event PlayerJoinEvent to EzChestShop v1.6.4

Every time a player joins the server it show this in the server console.

[11:15:35 ERROR]: Could not pass event PlayerJoinEvent to EzChestShop v1.6.4 java.lang.NullPointerException: Cannot invoke "me.deadlight.ezchestshop.utils.VersionUtils.injectConnection(org.bukkit.entity.Player)" because "me.deadlight.ezchestshop.utils.Utils.versionUtils" is null at me.deadlight.ezchestshop.listeners.PlayerJoinListener.onJoin(PlayerJoinListener.java:30) ~[EzChestShop-1.6.4.jar:?] at com.destroystokyo.paper.event.executor.asm.generated.GeneratedEventExecutor53.execute(Unknown Source) ~[?:?] at org.bukkit.plugin.EventExecutor$2.execute(EventExecutor.java:77) ~[paper-api-1.20.2-R0.1-SNAPSHOT.jar:?] at co.aikar.timings.TimedEventExecutor.execute(TimedEventExecutor.java:81) ~[paper-api-1.20.2-R0.1-SNAPSHOT.jar:git-Paper-224] at org.bukkit.plugin.RegisteredListener.callEvent(RegisteredListener.java:70) ~[paper-api-1.20.2-R0.1-SNAPSHOT.jar:?] at io.papermc.paper.plugin.manager.PaperEventManager.callEvent(PaperEventManager.java:54) ~[paper-1.20.2.jar:git-Paper-224] at io.papermc.paper.plugin.manager.PaperPluginManagerImpl.callEvent(PaperPluginManagerImpl.java:126) ~[paper-1.20.2.jar:git-Paper-224] at org.bukkit.plugin.SimplePluginManager.callEvent(SimplePluginManager.java:615) ~[paper-api-1.20.2-R0.1-SNAPSHOT.jar:?] at net.minecraft.server.players.PlayerList.placeNewPlayer(PlayerList.java:325) ~[paper-1.20.2.jar:git-Paper-224] at net.minecraft.server.network.ServerConfigurationPacketListenerImpl.handleConfigurationFinished(ServerConfigurationPacketListenerImpl.java:130) ~[paper-1.20.2.jar:git-Paper-224] at net.minecraft.network.protocol.configuration.ServerboundFinishConfigurationPacket.handle(ServerboundFinishConfigurationPacket.java:18) ~[paper-1.20.2.jar:git-Paper-224] at net.minecraft.network.protocol.configuration.ServerboundFinishConfigurationPacket.a(ServerboundFinishConfigurationPacket.java:9) ~[paper-1.20.2.jar:git-Paper-224] at net.minecraft.network.protocol.PacketUtils.lambda$ensureRunningOnSameThread$0(PacketUtils.java:53) ~[?:?] at net.minecraft.server.TickTask.run(TickTask.java:18) ~[paper-1.20.2.jar:git-Paper-224] at net.minecraft.util.thread.BlockableEventLoop.doRunTask(BlockableEventLoop.java:153) ~[?:?] at net.minecraft.util.thread.ReentrantBlockableEventLoop.doRunTask(ReentrantBlockableEventLoop.java:24) ~[?:?] at net.minecraft.server.MinecraftServer.doRunTask(MinecraftServer.java:1324) ~[paper-1.20.2.jar:git-Paper-224] at net.minecraft.server.MinecraftServer.d(MinecraftServer.java:193) ~[paper-1.20.2.jar:git-Paper-224] at net.minecraft.util.thread.BlockableEventLoop.pollTask(BlockableEventLoop.java:126) ~[?:?] at net.minecraft.server.MinecraftServer.pollTaskInternal(MinecraftServer.java:1301) ~[paper-1.20.2.jar:git-Paper-224] at net.minecraft.server.MinecraftServer.pollTask(MinecraftServer.java:1294) ~[paper-1.20.2.jar:git-Paper-224] at net.minecraft.util.thread.BlockableEventLoop.managedBlock(BlockableEventLoop.java:136) ~[?:?] at net.minecraft.server.MinecraftServer.waitUntilNextTick(MinecraftServer.java:1272) ~[paper-1.20.2.jar:git-Paper-224] at net.minecraft.server.MinecraftServer.runServer(MinecraftServer.java:1160) ~[paper-1.20.2.jar:git-Paper-224] at net.minecraft.server.MinecraftServer.lambda$spin$0(MinecraftServer.java:315) ~[paper-1.20.2.jar:git-Paper-224] at java.lang.Thread.run(Thread.java:833) ~[?:?]

Shulkerbox Stock

Short Description

Add additional storage to your shop by putting the item you want to sell into a shulkerbox inside the shop storage.

Implementation

  • Add a config setting to enable/disable this feature.
  • Update all methods that search the shop inventories to count items or do other stuff.
  • Update the buy/sell methods to take out/put in items into the additional stock.

Issue with hologram

Hello, i seem to have a problem regarding holograms.

Breaking and placing the shop at the same spot will retain the hologram item of the old shop. Video so you could understand better. Restarting the server seems to fix broken ones: https://streamable.com/kd6yvw

Using: EzChestShop-1.6.4
MC Version: 1.20.1

Players cannot use /sell?

Hello! I recently added this plugin to my paper server, and everything works fine! Until my player's tried to use /sell. At which point it said that they do not have permission to use the command. I've checked the perms multiple times and ecs and sell are both true. Idk what to do!

Transactions Messages

If I toggle my Transactions off there will still stand Transactionmessage: &aON

It is in the Locale_EN file on:

chat:
toggleTransactions:
MessageOn: '&7Transactionmessage: &aON'
MessageOff: '&7Transactionmessage: &cOFF'

Toggle admin GUI

Short Description

As a operator (/op) or as a person with ecs.admin / ecs.admin.view permission, you will always open a chest in admin view. In admin view you can buy/sell stuff to yourself and access other players settings/shop storage. This might not be wanted when recording a video or testing shops as a player. Here /ecsadmin toggleadminview comes into play.

Implementation

  • Implement a command to toggle admin view on and off (on by default).
  • Make sure the data is stored somewhere save like the database or a config file. This is data that will not change too often and not too many people are going to access it. Therefore a config file would work just fine. Using the default config is possible, however discussable as this is a setting for individuals, rather then server configurations, which means it's kinda out of place in the standard config.yml. Perhaps usersettings.yml or adminsettings.yml? If unsure comment on this issue and we'll come and discuss this.

Functionality Requests

Discussed in #63

Originally posted by XiroInfinity March 8, 2024
Hello, I was looking to suggest two features for use within this plugin, from the perspective of a player:

1: A "restricted" selling feature, where it simply limits the amount of purchases one can make to X per player on one's shops, deigned with the goal in mind of preventing monopolies on a marketplace, or maybe as a loss leader idea.

2: A gachapon/RNG system, where you can input many different items within a chest, and the buyer can pay X money for a random chance of getting any item within. The server I frequent uses "recipes" of varying values, and I have collected all available, but they still drop. The idea is that it could be sold to newer players who have none redeemed at random, where anything they acquire is of value to them but not so much to you.

Let me know if you have additional questions or concerns!

...as an aside, I don't know the viability, but if a 1.20.2 version can be done for these it would be largely appreciated.

created chest shop error

java.lang.NullPointerException: Cannot invoke "me.deadlight.ezchestshop.utils.ImprovedOfflinePlayer.fromOfflinePlayer(org.bukkit.OfflinePlayer)" because "me.deadlight.ezchestshop.utils.ImprovedOfflinePlayer.improvedOfflinePlayer" is null
at me.deadlight.ezchestshop.utils.XPEconomy.getXP(XPEconomy.java:9) ~[EzChestShop-1.6.5 (1).jar:?]
at me.deadlight.ezchestshop.utils.Utils.calculateBuyPossibleAmount(Utils.java:587) ~[EzChestShop-1.6.5 (1).jar:?]
at me.deadlight.ezchestshop.utils.Utils.calculatePossibleAmount(Utils.java:572) ~[EzChestShop-1.6.5 (1).jar:?]
at me.deadlight.ezchestshop.guis.AdminShopGUI.showGUI(AdminShopGUI.java:200) ~[EzChestShop-1.6.5 (1).jar:?]
at me.deadlight.ezchestshop.listeners.ChestOpeningListener.onChestOpening(ChestOpeningListener.java:132) ~[EzChestShop-1.6.5 (1).jar:?]
at com.destroystokyo.paper.event.executor.asm.generated.GeneratedEventExecutor36.execute(Unknown Source) ~[?:?]
at org.bukkit.plugin.EventExecutor$2.execute(EventExecutor.java:77) ~[purpur-api-1.20.2-R0.1-SNAPSHOT.jar:?]
at co.aikar.timings.TimedEventExecutor.execute(TimedEventExecutor.java:77) ~[purpur-api-1.20.2-R0.1-SNAPSHOT.jar:git-Purpur-2095]
at org.bukkit.plugin.RegisteredListener.callEvent(RegisteredListener.java:70) ~[purpur-api-1.20.2-R0.1-SNAPSHOT.jar:?]
at io.papermc.paper.plugin.manager.PaperEventManager.callEvent(PaperEventManager.java:54) ~[purpur-1.20.2.jar:git-Purpur-2095]
at io.papermc.paper.plugin.manager.PaperPluginManagerImpl.callEvent(PaperPluginManagerImpl.java:126) ~[purpur-1.20.2.jar:git-Purpur-2095]
at org.bukkit.plugin.SimplePluginManager.callEvent(SimplePluginManager.java:617) ~[purpur-api-1.20.2-R0.1-SNAPSHOT.jar:?]
at org.bukkit.craftbukkit.v1_20_R2.event.CraftEventFactory.callPlayerInteractEvent(CraftEventFactory.java:595) ~[purpur-1.20.2.jar:git-Purpur-2095]
at net.minecraft.server.level.ServerPlayerGameMode.useItemOn(ServerPlayerGameMode.java:538) ~[?:?]
at net.minecraft.server.network.ServerGamePacketListenerImpl.handleUseItemOn(ServerGamePacketListenerImpl.java:1978) ~[?:?]
at net.minecraft.network.protocol.game.ServerboundUseItemOnPacket.handle(ServerboundUseItemOnPacket.java:37) ~[?:?]
at net.minecraft.network.protocol.game.ServerboundUseItemOnPacket.a(ServerboundUseItemOnPacket.java:9) ~[?:?]
at net.minecraft.network.protocol.PacketUtils.lambda$ensureRunningOnSameThread$0(PacketUtils.java:53) ~[?:?]
at net.minecraft.server.TickTask.run(TickTask.java:18) ~[purpur-1.20.2.jar:git-Purpur-2095]
at net.minecraft.util.thread.BlockableEventLoop.doRunTask(BlockableEventLoop.java:153) ~[?:?]
at net.minecraft.util.thread.ReentrantBlockableEventLoop.doRunTask(ReentrantBlockableEventLoop.java:24) ~[?:?]
at net.minecraft.server.MinecraftServer.doRunTask(MinecraftServer.java:1351) ~[purpur-1.20.2.jar:git-Purpur-2095]
at net.minecraft.server.MinecraftServer.d(MinecraftServer.java:193) ~[purpur-1.20.2.jar:git-Purpur-2095]
at net.minecraft.util.thread.BlockableEventLoop.pollTask(BlockableEventLoop.java:126) ~[?:?]
at net.minecraft.server.MinecraftServer.pollTaskInternal(MinecraftServer.java:1328) ~[purpur-1.20.2.jar:git-Purpur-2095]
at net.minecraft.server.MinecraftServer.pollTask(MinecraftServer.java:1321) ~[purpur-1.20.2.jar:git-Purpur-2095]
at net.minecraft.util.thread.BlockableEventLoop.managedBlock(BlockableEventLoop.java:136) ~[?:?]
at net.minecraft.server.MinecraftServer.waitUntilNextTick(MinecraftServer.java:1299) ~[purpur-1.20.2.jar:git-Purpur-2095]
at net.minecraft.server.MinecraftServer.runServer(MinecraftServer.java:1187) ~[purpur-1.20.2.jar:git-Purpur-2095]
at net.minecraft.server.MinecraftServer.lambda$spin$0(MinecraftServer.java:320) ~[purpur-1.20.2.jar:git-Purpur-2095]
at java.lang.Thread.run(Thread.java:833) ~[?:?]

PLUGIN
> version EzChestShop
[01:03:45 INFO]: EzChestShop version 1.6.5
[01:03:45 INFO]: Easy Chest Shop that any server owner wants that for his/her players
[01:03:45 INFO]: Authors: ItzAmirreza and ElitoGame
SERVER
Current: git-Purpur-2095 (MC: 1.20.2)

this is bug?

Firework rocket store

If I create a chest store holding a crafted firework_rocket, it creates the store, but refuses to sell any of the inventory. I keep getting the error message of The shop is out of stock even when there's many stacks of rockets in the chest. The rocket that was used for this has a Flight duration: 1 metadata attached to it.

To manually craft this type of rocket: /give <user> firework_rocket 1 {Fireworks:{Flight:1}}

If I give a user a rocket using /give <user> firework_rocket (without any metadata) and then create a chest using that given rocket, the store builds and works correctly only as long as I put /give provided rockets in the chest (rockets without a Flight duration). Rockets created by crafting will not sell from the shop.

EzChestShop - version 1.5.0

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.