Giter Club home page Giter Club logo

junixapp / xpopup Goto Github PK

View Code? Open in Web Editor NEW
7.6K 101.0 1.1K 149.4 MB

🔥XPopup2.0版本重磅来袭,2倍以上性能提升,带来可观的动画性能优化和交互细节的提升!!!功能强大,交互优雅,动画丝滑的通用弹窗!可以替代Dialog,PopupWindow,PopupMenu,BottomSheet,DrawerLayout,Spinner等组件,自带十几种效果良好的动画, 支持完全的UI和动画自定义!(Powerful and Beautiful Popup for Android,can absolutely replace Dialog,PopupWindow,PopupMenu,BottomSheet,DrawerLayout,Spinner. With built-in animators , very easy to custom popup view.)

License: Apache License 2.0

Java 100.00%
popupwindow bottomsheet xpopup dialog drawerlayout popup popupmenu popup-window popup-dialog popupdialog

xpopup's Introduction

XPopup

国内Gitee镜像地址:https://gitee.com/lxj_gitee/XPopup

好站推荐

  1. 国内真正免费使用ChatGPT大模型的网站 https://www.hermchats.com?code=0D27F4D8
  2. 稳定科学上网工具:https://panel.dg1.top/#/register?code=59H7dLR3

中文 | English

  • 内置几种了常用的弹窗,十几种良好的动画,将弹窗和动画的自定义设计的极其简单;目前还没有出现XPopup实现不了的弹窗效果。 内置弹窗允许你使用项目已有的布局,同时还能用上XPopup提供的动画,交互和逻辑封装。
  • UI动画简洁,遵循Material Design,在设计动画的时候考虑了很多细节,过渡,层级的变化
  • 交互优雅,实现了优雅的手势交互,智能的嵌套滚动,智能的输入法交互,具体看Demo
  • 适配全面屏和各种挖孔屏,目前适配了小米,华为,谷歌,OPPO,VIVO,三星,魅族,一加全系全面屏手机
  • 自动监听Activity/Fragment生命周期或任意拥有Lifecycle的UI组件,自动释放资源。在Activity/Fragment直接finish的场景也避免了内存泄漏
  • XPopup实现了LifecycleOwner,可以直接被LiveData监视生命周期,弹窗可见时才更新数据,不可见不更新
  • 很好的易用性,自定义弹窗只需继承对应的类,实现你的布局,然后像Activity那样,在onCreate方法写逻辑即可
  • 性能优异,动画流畅;精心优化的动画,让你很难遇到卡顿场景
  • 支持在应用后台弹出(需要申请悬浮窗权限,一行代码即可)
  • 支持androidx,完美支持RTL布局,完美支持横竖屏切换,支持小窗模式
  • 如果你想要时间选择器和城市选择器,可以使用XPopup扩展功能库XPopupExt: https://github.com/li-xiaojun/XPopupExt

设计思路: 综合常见的弹窗场景,我将其分为几类:

  • Center类型,就是在中间弹出的弹窗,比如确认和取消弹窗,Loading弹窗
  • Bottom类型,就是从页面底部弹出,比如从底部弹出的分享窗体,知乎的从底部弹出的评论列表,内部已经处理好手势拖拽和嵌套滚动
  • Attach类型,就是弹窗的位置需要依附于某个View或者某个触摸点,就像系统的PopupMenu效果一样,但PopupMenu的自定义性很差,淘宝的商品列表筛选的下拉弹窗,微信的朋友圈点赞弹窗都是这种。
  • Drawer类型,就是从窗体的坐边或者右边弹出,并支持手势拖拽;好处是与界面解耦,可以在任何界面实现DrawerLayout效果
  • ImageViewer大图浏览类型,就像微信那样的图片浏览弹窗,带有良好的拖拽交互体验,内部嵌入了改良的PhotoView和subsampling-scale-imageview,支持加载超长长达图片并且不OOM
  • FullScreen类型,全屏弹窗,看起来和Activity一样,可以设置任意的动画器;适合用来实现登录,选择性的界面效果。
  • Position自由定位弹窗,弹窗是自由的,你可放在屏幕左上角,右下角,或者任意地方,结合强大的动画器,可以实现各种效果。

演示

内置弹窗(支持复用已有布局) 列表Center弹窗
Bottom列表弹窗(手势拖拽,横竖滚动) 自定义Bottom弹窗(天然支持嵌套滚动,多层弹窗)
Attach弹窗(动画优雅,智能定位,长按支持) 自定义Attach弹窗(任意方向支持,灵活易用)
Drawer弹窗(手势拖拽,状态栏阴影) 全屏弹窗(可作为Activity替代品,搭配十几个动画使用更佳)
Position自由定位弹窗(放在屏幕任意地方) 自定义贴在输入法之上的弹窗
PartShadow局部阴影弹窗 向上向下都可以
ImageViewer大图浏览弹窗(拖拽自然,如丝般顺滑) 超长图片,永不OOM(图像渐变过渡,优雅从容)
大图浏览弹窗,支持界面自定义 配合ViewPager使用
自定义弹窗和自定义动画 内置优雅美观的动画器,可搭配弹窗结合使用
应用后台弹出(一行代码实现权限申请) 联想搜索实现,轻而易举
气泡弹窗,横向和竖向已准备好!

快速体验

Gif录制的有些卡顿,真机预览效果更佳。扫描二维码下载Demo:

如果二维码图片不可见,点我下载Demo体验

Gradle

implementation 'com.github.li-xiaojun:XPopup:版本号看上面'

jitpack还要求在工程根目录的build.gradle中添加如下:

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

其中编译版本必须 >= 29:

compileSdkVersion 29

必须添加的依赖库,版本不用和我一致:

implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'

xpopup依赖了subsampling-scale-image-view, 如果你也依赖了这个库并且版本冲突,可以做个exclude即可:

implementation ('com.github.li-xiaojun:XPopup:版本号看上面'){
  exclude group: "com.davemorrissey.labs", module: "subsampling-scale-image-view-androidx"
}

使用文档

具体使用方法全在WIKI中,请查看下面各个章节:

混淆

-dontwarn com.lxj.xpopup.widget.**
-keep class com.lxj.xpopup.widget.**{*;}

谁在用XPopup

我本人很希望您能点击这里附上使用这个库的App名或者公司名,这样会给我更大的动力和热情去维护这个类库。

根据热心朋友提供的信息,目前使用XPopup的产品和公司有(70+):

打个赏

如果你觉得我帮助到了你,节省了你的时间,可以对我进行打赏(打赏时可以附上自己的大名和Github地址),金额随意,以表支持。

名字 Github地址
Alfred
LOPER7 https://github.com/loperSeven
microshow(RxFFmpeg作者) https://github.com/microshow

阿里云链接

如果有朋友购买阿里云服务器,可以点我的链接进入:

https://www.aliyun.com/minisite/goods?userCode=bak7qpav

有大量的代金券和折扣!!!

ECS-1核2G40G硬盘1M带宽 : 91元/年

ECS-2核4G40G硬盘1M带宽 : 414元/年

ECS-4核8G40G硬盘5M带宽 : 1046元/年

更多产品点击链接进入查看。。。

联系方式

Android开发交流群:783659607

QQ Email: [email protected]

QQ: 16167479

Licenses

 Copyright 2019 li-xiaojun

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.

xpopup's People

Contributors

junixapp 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  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

xpopup's Issues

自定义布局白色背景

你好,首先感谢你的开源。
我在使用过程中遇到这样一个问题:
我继承BottomPopupView 但是效果是背景透明的,我在我的布局父布局增加背景白色,xpop无法展示。
我尝试了 .hasShadowBg(false)仍然没有解决我的问题

点击按钮后能不能不关闭dialog?

能否暴露一个方法去控制当前dialog不关闭?例如输入对话框,在我输入的内容提交后服务器返回修改不成功,就不关闭当前对话框

提个建议

对于list类型的 不管是 bootom 还是 center 弹出 如果不重写 getMaxHeight 内容太多会充满整个屏幕 高度。如果重写了 getMaxHeight 如果内容太少 高度又并不是自适应的 而是 getMaxHeight的高度,多余的部分会留白。我看了源码 这是因为现在的 getMaxWidth 和 getMaxHeight 并没有用来作为界限值而是 给 弹窗设置了固定值。

因此我建议对于宽高的重写方法设计余下:
1:getMinWidth getMinHeight
2:getWidth getHeight
3:getMaxWidth getMaxHeight

如果重写了 2,那么就取固定宽高。否则 宽高 则在 1,3直接自适应变化

NullPointerException

版本号:1.3.7

使用代码:
private void showShopCardDialog() {
if (mShopCardPopupView == null) {
mShopCardPopupView = new ShopCardPopupView(this, mShopGoodsData);
mShopCardPopupView.setChangeLisenter(this);
} else {
mShopCardPopupView.refreshData();
}
XPopup.get(this)
.asCustom(mShopCardPopupView)
.dismissOnTouchOutside(true)
.atView(mViewLine)
.show();
}
操作场景:响应点击事件,当按钮被点击时展示,点击空白处时消失

错误信息:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.mmtc.beautytreasure, PID: 11746
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.ViewGroup.removeView(android.view.View)' on a null object reference
at com.lxj.xpopup.XPopup$2$2.run(XPopup.java:148)
at com.lxj.xpopup.core.BasePopupView$4.run(BasePopupView.java:301)
at android.os.Handler.handleCallback(Handler.java:808)
at android.os.Handler.dispatchMessage(Handler.java:101)
at android.os.Looper.loop(Looper.java:166)
at android.app.ActivityThread.main(ActivityThread.java:7529)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:245)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)

java.lang.NullPointerException: Attempt to read from field 'java.lang.Boolean com.lxj.xpopup.core.PopupInfo.isDismissOnBackPressed' on a null object reference

先看复现步骤视频:
https://github.com/parcool/resources/blob/master/device-2019-02-02-095005.mp4
错误信息:
java.lang.NullPointerException: Attempt to read from field 'java.lang.Boolean com.lxj.xpopup.core.PopupInfo.isDismissOnBackPressed' on a null object reference at com.lxj.xpopup.core.BasePopupView$1.onKey(BasePopupView.java:78) at android.view.View.dispatchKeyEvent(View.java:12465) at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1891) at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1896) at com.android.internal.policy.DecorView.superDispatchKeyEvent(DecorView.java:449) at com.android.internal.policy.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1820) at android.app.Activity.dispatchKeyEvent(Activity.java:3425) at android.support.v4.app.SupportActivity.superDispatchKeyEvent(ComponentActivity.java:108) at android.support.v4.view.KeyEventDispatcher.dispatchKeyEvent(KeyEventDispatcher.java:84) at android.support.v4.app.SupportActivity.dispatchKeyEvent(ComponentActivity.java:126) at android.support.v7.app.AppCompatActivity.dispatchKeyEvent(AppCompatActivity.java:534) at android.support.v7.view.WindowCallbackWrapper.dispatchKeyEvent(WindowCallbackWrapper.java:58) at android.support.v7.app.AppCompatDelegateImplBase$AppCompatWindowCallbackBase.dispatchKeyEvent(AppCompatDelegateImplBase.java:316) at com.android.internal.policy.DecorView.dispatchKeyEvent(DecorView.java:352) at android.view.ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl.java:5191) at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:5059) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4578) at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4631) at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4597) at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4737) at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4605) at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4794) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4578) at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4631) at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4597) at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4605) at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4578) at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4631) at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4597) at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4770) at android.view.ViewRootImpl$ImeInputStage.onFinishedInputEvent(ViewRootImpl.java:4933) at android.view.inputmethod.InputMethodManager$PendingEvent.run(InputMethodManager.java:2580) at android.view.inputmethod.InputMethodManager.invokeFinishedInputEventCallback(InputMethodManager.java:2090) at android.view.inputmethod.InputMethodManager.finishedInputEvent(InputMethodManager.java:2081) at android.view.inputmethod.InputMethodManager$ImeInputEventSender.onInputEventFinished(InputMethodManager.java:2557) at android.view.InputEventSender.dispatchInputEventFinished(InputEventSender.java:141) at android.os.MessageQueue.nativePollOnce(Native Method) at android.os.MessageQueue.next(MessageQueue.java:326) at android.os.Looper.loop(Looper.java:165) at android.app.ActivityThread.main(ActivityThread.java:6806) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)

BottomPopup位置问题

图1(手机上 小米mix2s)
image

图2(模拟器上)
image

注意底部,发现手机和模拟器出现的位置不同,一个像是针对decorationView,一个像是触发按钮的View?

提个建议

大图浏览都是用的String的URL当资源,局限性太大,换成Object?

建议

离职了一个月,今晚来看看github 发现您更新了很多内容,新增的很多新的功能。
这很赞,但是,您新增的功能我可能用不到,能否拆分成几个库,按需引用,就行SmartRefresh一样,基础功能一个库地址,扩展header一个库地址。

最新的版本号是多少

每次更新能不能把read me中引用改一下就行了,为啥每次让我们自己找版本号
implementation 'com.lxj:xpopup:latest release' 加载不了
用github的库第一次因为这个提issues

pop中图文样式

可不可以增加pop中文字或者图片的样式.比如text的居左对齐,居中对齐方式等,confirem的按钮颜色,而不是直接定义主色调.可以分开定义颜色.

一共有两个问题

第一,设置最大高度失效.我首先是设置了布局的最大高度maxheight,不行,然后我在代码中设置maxWidthAndHeight,依然不行.
第二,我如果强制高度为某一高度,listview数据就乱,实测自适应高度,等于全屏,listview数据不会有任何问题!

提个建议

可以修个按钮和信息字体大小和颜色

推荐加XPopup的QQ群:783659607,交流更方便!

优先看文档

文档已经非常详细的解释了如何使用,一些注意事项和常见问题的解决方式。所以具体的使用要先看文档。

编译出错问题

我的编译配置如下:

compileSdkVersion 26

dependencies {
    implementation 'com.android.support:appcompat-v7:26.0.0'
    implementation 'com.android.support:recyclerview-v7:26.0.0'
    implementation 'com.android.support:design:26.0.0'
}

如果你本地没有26版本的SDK,可能报错resource引用找不到或者其他编译类错误;
此时下载26的SDK重新编译即可,建议保持更新最新SDK。这个和你自己项目的编译版本完全可以不一样,关键是你本地要下26的SDK。

如何提Issue

如果您遇到问题,可以详细列出机型和系统版本,最好有代码和效果截图;如果只是XX手机上有什么问题,我还需要去找到这款手机去重现这个问题。我不可能找得到所有的手机,所以贴个代码和截图,很大程度我就能定位问题所在,谢谢了。

如果遇到问题,先运行我的Demo看看有没有问题。如果我的Demo没有问题,那么往往是你使用不当造成的;如果我的Demo也有问题,那么肯定就是Bug了。

点击事件执行时间

我希望点击事件触发dismiss 。dismiss动画执行完之后再执行点击逻辑。请问现有接口能否实现该想法

发现两个问题

1.自定义view继承PartShadowPopupView时候,如果自定义的view布局高度是match_parent的话,虚拟键会挡住一部分布局内容,应该是计算布局高度的时候没有计算是否存在虚拟键。
2.我使用Leaks的时候显示KeyboardUtils的onGlobalLayoutListener存在内存泄漏,是否应该提供一个remove方法来移除监听或者在view消失的时候自动移除监听呢

Navbar处理有问题,log发现返回的都展示存在navbar,但是实际并没有

// 如果有导航栏,则不能覆盖导航栏
if (hasNavBar(getContext())) { . //这是我自行修改过了
setPadding(0, 0, 0, Utils.getNavBarHeight());
}

/**
 * 检查是否存在虚拟按键栏
 */
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public static boolean hasNavBar(Context context) {
    Resources res = context.getResources();
    int resourceId = res.getIdentifier("config_showNavigationBar", "bool", "android");
    if (resourceId != 0) {
        boolean hasNav = res.getBoolean(resourceId);
        // check override flag
        String sNavBarOverride = getNavBarOverride();
        if ("1".equals(sNavBarOverride)) {
            hasNav = false;
        } else if ("0".equals(sNavBarOverride)) {
            hasNav = true;
        }
        return hasNav;
    } else { // fallback
        return !ViewConfiguration.get(context).hasPermanentMenuKey();
    }
}

/**
 * 判断虚拟按键栏是否重写
 */
private static String getNavBarOverride() {
    String sNavBarOverride = null;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        try {
            Class c = Class.forName("android.os.SystemProperties");
            Method m = c.getDeclaredMethod("get", String.class);
            m.setAccessible(true);
            sNavBarOverride = (String) m.invoke(null, "qemu.hw.mainkeys");
        } catch (Throwable e) {
        }
    }
    return sNavBarOverride;
}

动画问题

XPopup.get(context) .popupAnimation(PopupAnimation.TranslateAlphaFromBottom) .asCustom(SheetPopup(context,buildConfig()))

SheetPopup继承BottomPopupView
显示没有动画 内容也不见了

Margin的设置问题

继承BaseBottomPop后, 怎么修改bottomMarin? 默认的是贴边的.

尝试过在initPopupContent()
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getPopupImplView().getLayoutParams(); lp.bottomMargin = ScreenUtils.dp2px(20); getPopupImplView().setLayoutParams(lp);
没有效果,

也尝试过在getImplLayoutId的布局加marginBottom也是无效

混淆

需要设置什么吗?

不能在一个对话框结束后立马开启另一个对话框

试了下大概在sleep几百毫秒后可以出来,猜测可能是动画执行时间

例如这样的代码,loading框不会出来,在show前加上dismiss也不行:
XPopup.get(this).asInputConfirm("登录", "请输入账号", new OnInputConfirmListener() {
@OverRide
public void onConfirm(String text) {
XPopup.get(MainActivity.this).asLoading().show();
}
}).show();

使用dismiss监听,这样也不会出来,偶尔还会卡住:
XPopup.get(this).asInputConfirm("登录", "请输入账号", new OnInputConfirmListener() {
@OverRide
public void onConfirm(final String text) {
XPopup.get(MainActivity.this)
.setPopupCallback(new XPopupCallback() {
@OverRide
public void onShow() {

                                }

                                @Override
                                public void onDismiss() {
                                    XPopup.get(MainActivity.this).asLoading().show();
                                }
                            });
                }
            }).show();

自定义动画问题

我使用PopupAnimator,在initAnimator内设置 animateDuration=5000,背景的变化动画不是按照我设置5000来进行的。另外,我的show和dismiss动画时间不一样,能否animateDuration分成两个

华为手机弹出位置有问题

华为畅享9,不是从最底部弹出的,距离底部有个几十个像素吧。
这个手机是刘海屏,是不是这几十个像素是状态栏的高度?

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.