public interface IValueAdapter {
    String value2String(double value);
} public class LAnimator {

    float _hitValueY = 1;
    float _hitValueX = 1;

    LineChart _LineChart;

    public LAnimator(LineChart lineChart) {
        this._LineChart = lineChart;
    }


    public void animateX(long duration) {
        ValueAnimator animator = ObjectAnimator.ofFloat(0f, 1f);
        animator.setDuration(1000);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                _hitValueX = (float) animation.getAnimatedValue();

                _LineChart.postInvalidate();
            }
        });
        animator.start();
    }

    public void animateY(long duration) {
        ValueAnimator animator = ObjectAnimator.ofFloat(0f, 1f);
        animator.setDuration(1000);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                _hitValueY = (float) animation.getAnimatedValue();

                _LineChart.postInvalidate();
            }
        });
        animator.start();
    }

    public void animateXY(long duration) {
        ValueAnimator animator = ObjectAnimator.ofFloat(0f, 1f);
        animator.setDuration(1000);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                _hitValueX = (float) animation.getAnimatedValue();
                _hitValueY = _hitValueX;

                _LineChart.postInvalidate();
            }
        });
        animator.start();
    }

    public float get_hitValueY() {
        return _hitValueY;
    }

    public void set_hitValueY(float _hitValueY) {
        this._hitValueY = _hitValueY;
    }

    public float get_hitValueX() {
        return _hitValueX;
    }

    public void set_hitValueX(float _hitValueX) {
        this._hitValueX = _hitValueX;
    }
} public class Entry {

    double x;
    double y;

    boolean isNull_Y = false;

    public Entry(double x, double y) {
        this.x = x;
        this.y = y;
    }


    public double getX() {
        return x;
    }

    public Entry setX(double x) {
        this.x = x;
        return this;
    }

    public double getY() {
        return y;
    }

    public Entry setY(double y) {
        this.y = y;
        return this;
    }

    public boolean isNull_Y() {
        return isNull_Y;
    }

    public Entry setNull_Y() {
        isNull_Y = true;
        return this;
    }

    @Override
    public String toString() {
        return "entry x:" + x + " y:" + y;
    }
} public class Line {

    private List<Entry> entries = new ArrayList<>();


    private double mYMax = Double.MIN_VALUE;
    private double mYMin = Double.MAX_VALUE;
    private double mXMax = Double.MIN_VALUE;
    private double mXMin = Double.MAX_VALUE;

    private int lineColor = Color.BLACK;
    private int lineWidth = 1;
    private int circleColor = Color.RED;
    private int circleR = 4;

    private boolean isFilled = false;
    private int lineAlpha = 50;

    private boolean isEnable = true;
    private boolean isDrawCircle = true;
    private boolean isDrawLegend = false;
    private int legendWidth = 50;
    private int legendHeight = 17;
    private int legendTextSize = 8;
    private String name = "line";

    private CallBack_OnEntryClick onEntryClick;

    public Line() {
        this(null);
    }

    public Line(List<Entry> entries) {

        circleR = (int) Utils.dp2px(circleR);
        legendWidth = (int) Utils.dp2px(legendWidth);
        legendTextSize = (int) Utils.dp2px(legendTextSize);
        legendHeight = (int) Utils.dp2px(legendHeight);

        if (entries != null) {
            setEntries(entries);
        }
    }

    private void calMinMax(List<Entry> entries) {

        mYMax = -Double.MAX_VALUE;
        mYMin = Double.MAX_VALUE;
        mXMax = -Double.MAX_VALUE;
        mXMin = Double.MAX_VALUE;

        for (Entry entry : entries) {

            if (entry.getX() < mXMin) {
                mXMin = entry.getX();
            }
            if (entry.getX() > mXMax) {
                mXMax = entry.getX();
            }

            if (entry.getY() < mYMin) {
                mYMin = entry.getY();
            }
            if (entry.getY() > mYMax) {
                mYMax = entry.getY();
            }
        }
    }

    /**
     * 在数组的前面添加数据
     *
     * @param entry
     */
    public void addEntryInHead(Entry entry) {
        if (entries == null) {
            entries = new ArrayList<>();
        }
        entries.add(0, entry);

        // 计算最大最小
        if (entry.getX() < mXMin) {
            mXMin = entry.getX();
        }
        if (entry.getX() > mXMax) {
            mXMax = entry.getX();
        }

        if (entry.getY() < mYMin) {
            mYMin = entry.getY();
        }
        if (entry.getY() > mYMax) {
            mYMax = entry.getY();
        }
    }

    public void addEntry(Entry entry) {
        if (entries == null) {
            entries = new ArrayList<>();
        }
        entries.add(entry);

        // 计算最大最小
        if (entry.getX() < mXMin) {
            mXMin = entry.getX();
        }
        if (entry.getX() > mXMax) {
            mXMax = entry.getX();
        }

        if (entry.getY() < mYMin) {
            mYMin = entry.getY();
        }
        if (entry.getY() > mYMax) {
            mYMax = entry.getY();
        }
    }

    public List<Entry> getEntries() {
        return entries;
    }

    public void setEntries(List<Entry> entries) {
        // check
        if (entries == null) {
            throw new IllegalArgumentException("entries must be no null");
        }

        this.entries = entries;

        calMinMax(entries);
    }


    /**
     * 根据提供的 x数值，找出list中离它最近的数，返回其下标。
     * --------------
     * 考虑到性能的原因，采用二分查找法。(速度很快，不错！)
     *
     * @param entries
     * @param hitValue
     * @param rounding
     * @return
     */
    public static int getEntryIndex(List<Entry> entries, double hitValue, Rounding rounding) {

        if (entries == null || entries.isEmpty())
            return -1;

        int low = 0;
        int high = entries.size() - 1;
        int closet = low;

        while (low < high) {
            int m = (low + high) / 2;

            double fm = entries.get(m).getX();// middle
            double fr = entries.get(m + 1).getX();// right

            if (hitValue >= fm && hitValue <= fr) {

                // 中_右
                double d1 = Math.abs(hitValue - fm);
                double d2 = Math.abs(hitValue - fr);

                if (d1 <= d2) {
                    closet = m;
                } else {
                    closet = m + 1;
                }

                low = high = closet;
                break;

            } else if (hitValue < fm) {

                if (m >= 1) {
                    double fl = entries.get(m - 1).getX();// left

                    if (hitValue >= fl && hitValue <= fm) {
                        // 中_左
                        double d0 = Math.abs(hitValue - fl);
                        double d1 = Math.abs(hitValue - fm);

                        if (d0 <= d1) {
                            closet = m - 1;
                        } else {
                            closet = m;
                        }

                        low = high = closet;
                        break;
                    }
                }

                high = m - 1;
                closet = high;
            } else if (hitValue > fr) {
                low = m + 1;
                closet = low;
            }
        }

        // trick
        high++;
        low--;
        closet = Math.min(Math.max(closet, 0), entries.size() - 1);

        int result = low;
        if (rounding == Rounding.UP) {
            result = Math.min(high, entries.size() - 1);//多一个
        } else if (rounding == Rounding.DOWN) {
            result = Math.max(low, 0);//少一个
        } else if (rounding == Rounding.CLOSEST) {
            result = closet;
        }
        return result;
    }


    public double getmYMax() {
        return mYMax;
    }

    public void setmYMax(double mYMax) {
        this.mYMax = mYMax;
    }

    public double getmYMin() {
        return mYMin;
    }

    public void setmYMin(double mYMin) {
        this.mYMin = mYMin;
    }

    public double getmXMax() {
        return mXMax;
    }

    public void setmXMax(double mXMax) {
        this.mXMax = mXMax;
    }

    public double getmXMin() {
        return mXMin;
    }

    public void setmXMin(double mXMin) {
        this.mXMin = mXMin;
    }

    public int getLineColor() {
        return lineColor;
    }

    public void setLineColor(int lineColor) {
        this.lineColor = lineColor;
    }

    public int getLineWidth() {
        return lineWidth;
    }

    public void setLineWidth(int lineWidth) {
        this.lineWidth = lineWidth;
    }

    public boolean isEnable() {
        return isEnable;
    }

    public void setEnable(boolean enable) {
        isEnable = enable;
    }

    public boolean isDrawCircle() {
        return isDrawCircle;
    }

    public void setDrawCircle(boolean drawCircle) {
        isDrawCircle = drawCircle;
    }

    public int getLegendHeight() {
        return legendHeight;
    }

    public void setLegendHeight(int legendHeight) {
        this.legendHeight = legendHeight;
    }

    public int getLegendTextSize() {
        return legendTextSize;
    }

    public void setLegendTextSize(int legendTextSize) {
        this.legendTextSize = legendTextSize;
    }

    public int getLegendWidth() {
        return legendWidth;
    }

    public void setLegendWidth(int legendWidth) {
        this.legendWidth = legendWidth;
    }

    public boolean isDrawLegend() {
        return isDrawLegend;
    }

    public void setDrawLegend(boolean drawLegend) {
        isDrawLegend = drawLegend;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getCircleR() {
        return circleR;
    }

    public void setCircleR(int circleR) {
        this.circleR = circleR;
    }

    public int getCircleColor() {
        return circleColor;
    }

    public void setCircleColor(int circleColor) {
        this.circleColor = circleColor;
    }

    public CallBack_OnEntryClick getOnEntryClick() {
        return onEntryClick;
    }

    public void setOnEntryClick(CallBack_OnEntryClick onEntryClick) {
        this.onEntryClick = onEntryClick;
    }

    public boolean isFilled() {
        return isFilled;
    }

    public void setFilled(boolean filled) {
        isFilled = filled;
    }

    public int getLineAlpha() {
        return lineAlpha;
    }

    public void setLineAlpha(int lineAlpha) {
        this.lineAlpha = lineAlpha;
    }

    public enum Rounding {
        UP,
        DOWN,
        CLOSEST,
    }

    public interface CallBack_OnEntryClick {
        void onEntry(Line line, Entry entry);
    }

} public class Lines {

    private List<Line> lines = new ArrayList<>();

    /************************  x,y的范围  **********************/
    private double mYMax;
    private double mYMin;
    private double mXMax;
    private double mXMin;

    /**
     * 是否使用 设置的最小最大值
     */
    private boolean isYCustomMaxMin = false;
    /**
     * 是否使用 设置的最小最大值
     */
    private boolean isXCustomMaxMin = false;

    public Lines() {
    }

    public Lines(List<Line> lines) {
        setLines(lines);
    }

    public List<Line> getLines() {
        return lines;
    }

    public void setLines(List<Line> lines) {
        this.lines = lines;
        calMinMax(lines);
    }

    public void addLine(Line line) {
        lines.add(line);
        calMinMax(lines);
    }

    private void calMinMax(List<Line> lines) {

        if (!isXCustomMaxMin) {
            mXMax = Double.MIN_VALUE;
            mXMin = Double.MAX_VALUE;
        }

        if (!isYCustomMaxMin) {
            mYMax = Double.MIN_VALUE;
            mYMin = Double.MAX_VALUE;
        }

        for (Line line : lines) {

            if (!isXCustomMaxMin) {
                if (line.getmXMin() < mXMin) {
                    mXMin = line.getmXMin();
                }
                if (line.getmXMax() > mXMax) {
                    mXMax = line.getmXMax();
                }
            }

            if (!isYCustomMaxMin) {
                if (line.getmYMin() < mYMin) {
                    mYMin = line.getmYMin();
                }
                if (line.getmYMax() > mYMax) {
                    mYMax = line.getmYMax();
                }
            }
        }

        // 考虑到只有一个点的问题
        if (mXMax == mXMin) {
            double half = Math.abs(mXMax) / 2;
            mXMax += half;
            mXMin -= half;
        }

        if (mYMax == mYMin) {
            double half = Math.abs(mYMax) / 2;
            mYMax += half;
            mYMin -= half;
        }
    }

    public void calMinMax() {

        if (lines != null) {
            calMinMax(lines);
        }
    }


    public double getmYMax() {
        return mYMax;
    }

    public void setmYMax(double mYMax) {
        this.mYMax = mYMax;
    }

    public double getmYMin() {
        return mYMin;
    }

    public void setmYMin(double mYMin) {
        this.mYMin = mYMin;
    }

    public double getmXMax() {
        return mXMax;
    }

    public void setmXMax(double mXMax) {
        this.mXMax = mXMax;
    }

    public double getmXMin() {
        return mXMin;
    }

    public void setmXMin(double mXMin) {
        this.mXMin = mXMin;
    }

    public boolean isYCustomMaxMin() {
        return isYCustomMaxMin;
    }

    public void setYCustomMaxMin(boolean customMaxMin) {
        isYCustomMaxMin = customMaxMin;
    }

    public boolean isXCustomMaxMin() {
        return isXCustomMaxMin;
    }

    public void setXCustomMaxMin(boolean XCustomMaxMin) {
        isXCustomMaxMin = XCustomMaxMin;
    }
} public interface IDragListener {
    /**
     * @param xMin 当前图谱中可见的x轴上的最小值
     * @param xMax 当前图谱中可见的x轴上的最大值
     */
    void onDrag(double xMin, double xMax);
} public class MappingManager {

    /**
     * 区域视图
     */
    RectF _contentRect;

    /**
     * 数据视图
     */
    RectD _maxViewPort;
    RectD _currentViewPort;


    /**
     * 数据视图的约束
     */
    float fatFactor = 1.3f;//肥胖因子
    RectD _constrainViewPort;

    boolean current2Fat; // 当前视图是否跟约束视图一样大


    public MappingManager(RectF rectMain) {
        _contentRect = rectMain;
        _maxViewPort = new RectD();
        _currentViewPort = new RectD();
        _constrainViewPort = new RectD();
    }

    public void prepareRelation(double xMin, double xMax, double yMin, double yMax) {

        _maxViewPort.left = xMin;
        _maxViewPort.right = xMax;
        _maxViewPort.bottom = yMin;
        _maxViewPort.top = yMax;

        _currentViewPort.setRectD(_maxViewPort);

        setFatFactor(fatFactor);
    }

    /**
     * 根据像素位置变换成数值
     *
     * @param x
     * @param y
     * @return
     */
    public SingleD_XY getValueByPx(float x, float y) {
        SingleD_XY value = SingleD_XY.getInstance();
        value.setX(p2v_x(x)).setY(p2v_y(y));
        return value;
    }

    public SingleF_XY getPxByEntry(Entry entry) {
        return getPxByValue(entry.getX(), entry.getY());
    }

    /**
     * 根据数值变换成像素
     *
     * @param x
     * @param y
     * @return
     */
    public SingleF_XY getPxByValue(double x, double y) {

        SingleF_XY value = SingleF_XY.getInstance();
        value.setX(v2p_x(x)).setY(v2p_y(y));
        return value;
    }

    /**
     * x方向
     * ---------------
     * 数值转换成像素
     *
     * @param xValue
     * @return
     */
    public float v2p_x(double xValue) {
        double px = _contentRect.left + _contentRect.width() * (xValue - _currentViewPort.left) / _currentViewPort.width();
        return (float) px;
    }

    /**
     * y方向
     * ----------------
     * 数值转换成像素
     *
     * @param yValue
     * @return
     */
    public float v2p_y(double yValue) {
        double py = _contentRect.top + _contentRect.height() - _contentRect.height() * (yValue - _currentViewPort.bottom) / _currentViewPort.height();
        return (float) py;
    }

    /**
     * x方向
     * ---------------
     * 像素转换成数值
     *
     * @param xPix
     * @return
     */
    public double p2v_x(float xPix) {
        double value = xPix - _contentRect.left;
        value = value / _contentRect.width() * _currentViewPort.width();
        value = value + _currentViewPort.left;
        return value;
    }

    /**
     * y方向
     * --------------
     * 像素转换成数值
     *
     * @param yPix
     * @return
     */
    public double p2v_y(float yPix) {
        double value = yPix - (_contentRect.top + _contentRect.height());
        value = value / -_contentRect.height() * _currentViewPort.height();
        value = value + _currentViewPort.bottom;
        return value;
    }


    public void zoom(float scaleX, float scaleY, float cx, float cy) {

        double newWidth = scaleX * _currentViewPort.width();
        double newHeight = scaleY * _currentViewPort.height();

        double hitValueX = p2v_x(cx);
        double left = hitValueX - newWidth * (cx - _contentRect.left) / _contentRect.width();

        double hitValueY = p2v_y(cy);
        double bottom = hitValueY - newHeight * (_contentRect.bottom - cy) / _contentRect.height();

        _currentViewPort.left = left;
        _currentViewPort.bottom = bottom;
        _currentViewPort.right = _currentViewPort.left + newWidth;
        _currentViewPort.top = _currentViewPort.bottom + newHeight;

        constrainViewPort(_currentViewPort);
    }

    public void zoom(float level, double oldStartW, double oldStartH, float cx, float cy, boolean canX, boolean canY) {

        double newWidth = canX ? oldStartW * level : oldStartW;
        double newHeight = canY ? oldStartH * level : oldStartH;

        double hitValueX = p2v_x(cx);
        double left = hitValueX - newWidth * (cx - _contentRect.left) / _contentRect.width();

        double hitValueY = p2v_y(cy);
        double bottom = hitValueY - newHeight * (_contentRect.bottom - cy) / _contentRect.height();

        _currentViewPort.left = left;
        _currentViewPort.bottom = bottom;
        _currentViewPort.right = _currentViewPort.left + newWidth;
        _currentViewPort.top = _currentViewPort.bottom + newHeight;

        constrainViewPort(_currentViewPort);
    }

    /**
     * 根据移动的像素偏离---》计算出数据视图的偏离
     *
     * @param dx
     * @param dy
     */
    public void translate(float dx, float dy) {

        double w = _currentViewPort.width();
        double h = _currentViewPort.height();

        double ddx = w * dx / _contentRect.width();
        double ddy = h * dy / _contentRect.height();

        _currentViewPort.left += -ddx;
        _currentViewPort.bottom += ddy;

        // 约束 currentViewPort 的位置
        constrainViewPort(_currentViewPort, w, h);

        _currentViewPort.right = _currentViewPort.left + w;
        _currentViewPort.top = _currentViewPort.bottom + h;
    }

    /**
     * 约束当前的viewport
     * ---------------------
     * 针对平移的方式
     */
    private void constrainViewPort(RectD currentViewPort, double currentWidth, double currentHeight) {

        currentViewPort.left = Math.max(_constrainViewPort.left, Math.min(_constrainViewPort.right - currentWidth, currentViewPort.left));
        currentViewPort.bottom = Math.max(_constrainViewPort.bottom, Math.min(_constrainViewPort.top - currentHeight, currentViewPort.bottom));
    }

    /**
     * 约束当前的viewport
     * ---------------------
     * 针对缩放的方式
     *
     * @param currentViewPort
     */
    private void constrainViewPort(RectD currentViewPort) {

        currentViewPort.left = Math.max(_constrainViewPort.left, currentViewPort.left);
        currentViewPort.right = Math.min(_constrainViewPort.right, currentViewPort.right);

        currentViewPort.bottom = Math.max(_constrainViewPort.bottom, currentViewPort.bottom);
        currentViewPort.top = Math.min(_constrainViewPort.top, currentViewPort.top);
    }

    public RectD get_constrainViewPort() {
        return _constrainViewPort;
    }

    public void set_constrainViewPort(RectD _constrainViewPort) {
        this._constrainViewPort = _constrainViewPort;
    }

    public RectD get_maxViewPort() {
        return _maxViewPort;
    }

    public void set_maxViewPort(RectD _maxViewPort) {
        this._maxViewPort = _maxViewPort;
    }

    public RectD get_currentViewPort() {
        return _currentViewPort;
    }

    public void set_currentViewPort(RectD _currentViewPort) {
        this._currentViewPort = _currentViewPort;
    }

    public float getFatFactor() {
        return fatFactor;
    }


    public boolean isCurrent2Fat() {
        return current2Fat;
    }

    /**
     * 当前视图是否跟约束视图一样大
     *
     * @param current2Fat
     */
    public void setCurrent2Fat(boolean current2Fat) {
        this.current2Fat = current2Fat;
    }


    /**
     * 设置约束视图的肥胖因子
     * -------------------
     * 和最大的数据视图做比较:
     * 1. 约束视图 是 最大视图的 1.1 倍，那么这个肥胖因子就是 1.1
     *
     * @param fatFactor
     */
    public void setFatFactor(float fatFactor) {

        if (fatFactor < 1) {
            throw new RuntimeException("肥胖因子必须大于1！");
        }

        this.fatFactor = fatFactor;

        double w = _maxViewPort.width();
        double h = _maxViewPort.height();

        fatFactor = fatFactor - 1;

        _constrainViewPort.left = _maxViewPort.left - w * fatFactor;
        _constrainViewPort.right = _maxViewPort.right + w * fatFactor;
        _constrainViewPort.top = _maxViewPort.top + h * fatFactor;
        _constrainViewPort.bottom = _maxViewPort.bottom - h * fatFactor;

        if (current2Fat)
            _currentViewPort.setRectD(_constrainViewPort);
        else
            _currentViewPort.setRectD(_maxViewPort);
    }

} public class HighLight {

    public static final float D_HIGHLIGHT_WIDTH = 1;
    public static final float D_HINT_TEXT_SIZE = 15;

    //////////////////////// high light ////////////////////////
    int highLightColor = Color.RED;
    float highLightWidth;

    //////////////////////// hint ////////////////////////
    int hintColor = Color.BLACK;
    float hintTextSize;

    boolean enable = false;
    boolean isDrawHighLine = true;
    boolean isDrawHint = true;

    protected IValueAdapter xValueAdapter;// 高亮时，x应该如何显示？
    protected IValueAdapter yValueAdapter;// 高亮时，y应该如何显示？


    public HighLight() {

        highLightWidth = Utils.dp2px(D_HIGHLIGHT_WIDTH);
        hintTextSize = Utils.dp2px(D_HINT_TEXT_SIZE);

        xValueAdapter = new DefaultHighLightValueAdapter();
        yValueAdapter = new DefaultHighLightValueAdapter();
    }

    public HighLight(IValueAdapter xValueAdapter, IValueAdapter yValueAdapter) {
        this.xValueAdapter = xValueAdapter;
        this.yValueAdapter = yValueAdapter;
    }

    public int getHighLightColor() {
        return highLightColor;
    }

    public void setHighLightColor(int highLightColor) {
        this.highLightColor = highLightColor;
    }

    public float getHighLightWidth() {
        return highLightWidth;
    }

    public void setHighLightWidth(float highLightWidth) {
        this.highLightWidth = highLightWidth;
    }

    public int getHintColor() {
        return hintColor;
    }

    public void setHintColor(int hintColor) {
        this.hintColor = hintColor;
    }

    public float getHintTextSize() {
        return hintTextSize;
    }

    public void setHintTextSize(float hintTextSize) {
        this.hintTextSize = hintTextSize;
    }

    public boolean isEnable() {
        return enable;
    }

    public void setEnable(boolean enable) {
        this.enable = enable;
    }

    public boolean isDrawHighLine() {
        return isDrawHighLine;
    }

    public void setDrawHighLine(boolean drawHighLine) {
        isDrawHighLine = drawHighLine;
    }

    public boolean isDrawHint() {
        return isDrawHint;
    }

    public void setDrawHint(boolean drawHint) {
        isDrawHint = drawHint;
    }

    public IValueAdapter getxValueAdapter() {
        return xValueAdapter;
    }

    public HighLight setxValueAdapter(IValueAdapter xValueAdapter) {
        this.xValueAdapter = xValueAdapter;
        return this;
    }

    public IValueAdapter getyValueAdapter() {
        return yValueAdapter;
    }

    public HighLight setyValueAdapter(IValueAdapter yValueAdapter) {
        this.yValueAdapter = yValueAdapter;
        return this;
    }
} public class XAxis extends Axis {

    public static final float AREA_UNIT = 14;// unit 区域的高
    public static final float AREA_LABEL = 14;// label 区域的高


    public XAxis() {
        super();

        labelDimen = Utils.dp2px(AREA_LABEL);
        unitDimen = Utils.dp2px(AREA_UNIT);
    }
} public class YAxis extends Axis {

    public static final float AREA_UNIT = 14;// unit 区域的高
    public static final float AREA_LABEL = 14;// label 区域的高


    public YAxis() {
        super();

        labelDimen = Utils.dp2px(AREA_LABEL);
        unitDimen = Utils.dp2px(AREA_UNIT);
    }

} public class GodRender extends BaseRender {

    Paint _PaintRect;
    RectF _GodRect = new RectF(_rectMain);

    public GodRender(RectF _rectMain, MappingManager _MappingManager, RectF godRect) {
        super(_rectMain, _MappingManager);

        this._GodRect = godRect;

        _PaintRect = new Paint(Paint.ANTI_ALIAS_FLAG);
        _PaintRect.setColor(Color.RED);
        _PaintRect.setStrokeWidth(Utils.dp2px(5));
        _PaintRect.setStyle(Paint.Style.STROKE);
    }


    public void render(Canvas canvas) {
        canvas.drawRect(_GodRect, _PaintRect);
    }


} public class HighLightRender extends BaseRender {

    private final static double UN_CHOOSE = Double.MIN_VALUE;
    public static final int H1 = 1;// 光标1
    public static final int H2 = 2;// 光标2

    int which = H1;

    Lines _lines;
    HighLight _highLight;

    Paint paintHighLight;
    Paint paintHint;

    LineChart lineChart;

    public HighLightRender(LineChart lineChart, int which, RectF rectMain, MappingManager _MappingManager, HighLight highLight) {
        super(rectMain, _MappingManager);

        this.lineChart = lineChart;
        this.which = which;
        this._highLight = highLight;

        paintHighLight = new Paint(Paint.ANTI_ALIAS_FLAG);
        paintHint = new Paint(Paint.ANTI_ALIAS_FLAG);
    }


    double highValueX = UN_CHOOSE;
    double hightValueY = UN_CHOOSE;

    int record_LineIndex = -1;// 记录哪条线
    int record_EntryIndex = -1;// 记录哪条entry
    boolean isClick = false;

    public void highLight_ValueXY(double x, double y) {
        highValueX = x;
        hightValueY = y;
        isClick = true;
    }

    public void highLightLeft() {
        record_EntryIndex--;
    }

    public void highLightRight() {
        record_EntryIndex++;
    }


    public void render(Canvas canvas) {

        if (!_highLight.isEnable()) {
            return;
        }

        _lines = lineChart.getlines();

        if (_lines == null) {
            return;
        }

        if (_lines.getLines().size() == 0) {
            return;
        }

        if (highValueX == UN_CHOOSE || hightValueY == UN_CHOOSE) {
            return;
        }

        canvas.save();
        canvas.clipRect(_rectMain); // 限制绘制区域

        drawHighLight_Hint(canvas);

        canvas.restore();
    }


    private void drawHighLight_Hint(Canvas canvas) {

        double closeX = Double.MAX_VALUE;
        double closeY = Double.MAX_VALUE;
        Entry hitEntry = null;

        if (isClick) {
            // 点击光标进来的
            for (int i = 0; i < _lines.getLines().size(); i++) {
                Line line = _lines.getLines().get(i);
                if (!line.isEnable()) {
                    continue;
                }
                int indexEntry = Line.getEntryIndex(line.getEntries(), highValueX, Line.Rounding.CLOSEST);

                if (indexEntry < 0 || indexEntry >= line.getEntries().size()) {
                    continue;
                }

                Entry entry = line.getEntries().get(indexEntry);

                // 到点击的距离
                double dx = Math.abs(entry.getX() - highValueX);
                double dy = Math.abs(entry.getY() - hightValueY);

                // 先考虑 x
                if (dx <= closeX) {
                    closeX = dx;
                    // 再考虑 y
                    if (dy <= closeY) {
                        closeY = dy;
                        hitEntry = entry;
                        record_EntryIndex = indexEntry;
                        record_LineIndex = i;
                    }
                }
            }
        } else {
            // 左右移动进来的
            try {
                Line line = _lines.getLines().get(record_LineIndex);
                if (line.isEnable()) {
                    hitEntry = line.getEntries().get(record_EntryIndex);
                }
            } catch (Exception e) {
                hitEntry = null;
            }
        }


        if (hitEntry == null) {
            return;
        }

        // 考虑断掉的点
        if (hitEntry.isNull_Y()) {
            return;
        }

        highValueX = hitEntry.getX();// real indexX

        SingleF_XY xy = _MappingManager.getPxByEntry(hitEntry);


        // draw high line
        if (_highLight.isDrawHighLine()) {
            paintHighLight.setStrokeWidth(_highLight.getHighLightWidth());
            paintHighLight.setColor(_highLight.getHighLightColor());

            canvas.drawLine(_rectMain.left, xy.getY(), _rectMain.right, xy.getY(), paintHighLight);
            canvas.drawLine(xy.getX(), _rectMain.top, xy.getX(), _rectMain.bottom, paintHighLight);
        }

        // draw hint
        if (_highLight.isDrawHint()) {
            paintHint.setColor(_highLight.getHintColor());
            paintHint.setTextSize(_highLight.getHintTextSize());

            String xStr = _highLight.getxValueAdapter().value2String(hitEntry.getX());
            String yStr = _highLight.getyValueAdapter().value2String(hitEntry.getY());
            float txtHeight = Utils.textHeight(paintHint);
            float txtWidth = Math.max(Utils.textWidth(paintHint, xStr), Utils.textWidth(paintHint, yStr));

            float x = 0, y = 0;

            if (which == H1) {
                x = _rectMain.right - txtWidth - 10;
                y = _rectMain.top + Utils.dp2px(20);
            } else if (which == H2) {
                x = _rectMain.right - txtWidth - 10;
                y = _rectMain.top + Utils.dp2px(20) + txtHeight * 2 + Utils.dp2px(5);

                // delta x
                double x1 = lineChart.get_HighLightRender1().highValueX;
                double x2 = highValueX;

                float y_delta = y + txtHeight * 2 + Utils.dp2px(5);
                String delta = "ΔX: " + Math.abs(x2 - x1);
                canvas.drawText(delta, x, y_delta, paintHint);
            }


            canvas.drawText(xStr, x, y, paintHint);
            canvas.drawText(yStr, x, y + txtHeight, paintHint);
        }

        // callback
        if (isClick) {
            isClick = false;

            Line line = _lines.getLines().get(record_LineIndex);
            hitEntry = line.getEntries().get(record_EntryIndex);

            Line.CallBack_OnEntryClick cb = line.getOnEntryClick();
            if (cb != null) {
                cb.onEntry(line, hitEntry);
            }
        }

    }

} public class LineRender extends BaseRender {

    LineChart lineChart;
    Lines _lines;

    Paint _PaintLine;
    Paint _PaintCircle;
    Paint _PaintLegend;

    Path _PathLine;

    LAnimator _LAnimator;

    Bitmap _drawBitmap;
    Canvas softwareCanvas = new Canvas();

    public LineRender(RectF rectMain, MappingManager _MappingManager, Lines _lines, LineChart lineChart) {
        super(rectMain, _MappingManager);
        this._lines = _lines;
        this.lineChart = lineChart;
        this._LAnimator = lineChart.get_LAnimator();

        _PaintLine = new Paint(Paint.ANTI_ALIAS_FLAG);
        _PaintCircle = new Paint(Paint.ANTI_ALIAS_FLAG);
        _PaintLegend = new Paint(Paint.ANTI_ALIAS_FLAG);

        _PathLine = new Path();
    }


    public void onChartSizeChanged(int w, int h) {
        if (w > 0 && h > 0) {
            _drawBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
            softwareCanvas.setBitmap(_drawBitmap);
        }
    }


    public void render(Canvas canvasSrc) {

        if (_lines == null) {
            return;
        }

        Canvas canvas = null;

        // layout editor 状态下 _drawBitmap 是没有的
        if (null != _drawBitmap) {
            canvas = softwareCanvas;
            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
        } else {
            canvas = canvasSrc;
        }


        canvas.save();
        canvas.clipRect(_rectMain); // 限制绘制区域

        // 绘制折线 和 折线上的圆点
        for (Line line : _lines.getLines()) {
            if (!line.isEnable()) {
                continue;
            }
            drawLine_Circle(canvas, line);
        }

        canvas.restore();

        // 绘制每条线的 legend
        for (int i = 0; i < _lines.getLines().size(); i++) {
            Line line = _lines.getLines().get(i);
            if (!line.isEnable()) {
                continue;
            }
            drawLegend(canvas, line, i);
        }

        if (_drawBitmap != null) {
            canvasSrc.drawBitmap(_drawBitmap, 0, 0, null);
        }
    }

    private void drawLine_Circle(Canvas canvas, Line line) {

        _PaintLine.setStrokeWidth(line.getLineWidth());
        _PaintLine.setColor(line.getLineColor());
        _PaintLine.setStyle(Paint.Style.STROKE);

        _PaintCircle.setStrokeWidth(line.getLineWidth());
        _PaintCircle.setColor(line.getLineColor());

        List<Entry> list = line.getEntries();

        // check
        if (list == null || list.size() == 0) {
            return;
        }

        // 考虑只有一个点的情况
        if (list.size() == 1) {
            Entry hit = list.get(0);
            SingleF_XY xy = _MappingManager.getPxByEntry(hit);
            canvas.drawCircle(xy.getX(), xy.getY(), line.getCircleR(), _PaintCircle);
            return;
        }

        double xMin_Visiable = lineChart.getVisiableMinX();
        double xMax_Visiable = xMin_Visiable + (lineChart.getVisiableMaxX() - xMin_Visiable) * _LAnimator.get_hitValueX();// 考虑动画

        int minIndex = Line.getEntryIndex(list, xMin_Visiable, Line.Rounding.DOWN);
        int maxIndex = Line.getEntryIndex(list, xMax_Visiable, Line.Rounding.UP);

        if (Math.abs(maxIndex - minIndex) == 0) {
            return;
        }

        if ((maxIndex - minIndex) > 1500) {
            _PaintLine.setAntiAlias(false);// 考虑性能
        } else {
            _PaintLine.setAntiAlias(true);
        }

        _PathLine.reset();

        int breakI = -1;

        for (int i = minIndex; i <= maxIndex; i++) {
            Entry entry = list.get(i);

            // 考虑这个点是否断掉
            if (entry.isNull_Y()) {
                breakI = i;
                continue;
            }

            float sx = _MappingManager.v2p_x(entry.getX());
            float sy = getAnimateY(_MappingManager.v2p_y(entry.getY()));

            if (i == minIndex || i == breakI + 1) {
                _PathLine.moveTo(sx, sy);
            } else {
                _PathLine.lineTo(sx, sy);
            }

            if (line.isDrawCircle()) {
                SingleF_XY xy = _MappingManager.getPxByEntry(entry);
                canvas.drawCircle(xy.getX(), getAnimateY(xy.getY()), line.getCircleR(), _PaintCircle);

                // 把最后点一个绘制出来
                if (i == maxIndex - 1) {
                    Entry last = list.get(maxIndex);
                    xy = _MappingManager.getPxByEntry(last);
                    /////////////////////////////////////////////  考虑动画效果  //////////////////////////////////////////
                    canvas.drawCircle(xy.getX(), getAnimateY(xy.getY()), line.getCircleR(), _PaintCircle);
                }
            }
        }

        canvas.drawPath(_PathLine, _PaintLine);

        // 有断掉的点，就不考虑填充了
        if (breakI != -1) {
            return;
        }

        // 考虑填充
        if (line.isFilled()) {
            Entry start = list.get(minIndex);
            Entry end = list.get(maxIndex);
            float sx = _MappingManager.v2p_x(start.getX());
            float sy = getAnimateY(_MappingManager.v2p_y(start.getY()));
            float ex = _MappingManager.v2p_x(end.getX());
            float ey = getAnimateY(_MappingManager.v2p_y(end.getY()));

            float maxY = Math.max(sy, ey);

            _PathLine.lineTo(ex, lineChart.get_MainPlotRect().bottom);
            _PathLine.lineTo(sx, lineChart.get_MainPlotRect().bottom);

            _PathLine.close();

            _PaintLine.setStyle(Paint.Style.FILL);
            _PaintLine.setAlpha(50);

            Shader shader = new LinearGradient(0, maxY, 0, 0, line.getLineColor(), line.getLineColor() / 50, Shader.TileMode.CLAMP);
            _PaintLine.setShader(shader);

            canvas.drawPath(_PathLine, _PaintLine);
            _PaintLine.setStyle(Paint.Style.STROKE);
            _PaintLine.setShader(null);
        }
    }


    RectF _RectFBuffer = new RectF();

    private void drawLegend(Canvas canvas, Line line, int order) {

        if (!line.isDrawLegend()) {
            return;
        }

        _PaintLegend.setColor(line.getLineColor());
        _PaintLegend.setTextSize(line.getLegendTextSize());

        String name = line.getName();
        float txtH = Utils.textHeight(_PaintLegend);
        float cubic = 20;

        _RectFBuffer.left = order * line.getLegendWidth();
        _RectFBuffer.top = line.getLegendHeight() - cubic - txtH / 2;
        _RectFBuffer.right = _RectFBuffer.left + cubic;
        _RectFBuffer.bottom = _RectFBuffer.top + cubic;


        canvas.drawRect(_RectFBuffer, _PaintLegend);
        canvas.drawText(name, _RectFBuffer.right + 10, _RectFBuffer.bottom, _PaintLegend);
    }

    public void onDataChanged(Lines lines) {
        _lines = lines;
    }

    private float getAnimateY(float src) {
        return lineChart.get_MainPlotRect().bottom - (lineChart.get_MainPlotRect().bottom - src) * _LAnimator.get_hitValueY();
    }
} public class NoDataRender extends BaseRender {

    Paint _Paint;
    String txt = "无数据";
    int txtColor = Color.RED;
    int txtSize = 30;

    public NoDataRender(RectF _FrameManager, MappingManager _MappingManager) {
        super(_FrameManager, _MappingManager);

        _Paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    }
    
    public void render(Canvas canvas) {

        _Paint.setColor(txtColor);
        _Paint.setTextSize(txtSize);

        float x = _rectMain.centerX();
        float y = _rectMain.centerY();

        float halfW = Utils.textWidth(_Paint, txt) / 2;
        float halfH = Utils.textHeightAsc(_Paint) / 2;

        canvas.drawText(txt, x - halfW, y + halfH, _Paint);
    }
} public class XAxisRender extends AxisRender {

    public XAxisRender(RectF _FrameManager, MappingManager _MappingManager, Axis axis) {
        super(_FrameManager, _MappingManager, axis);
    }

    @Override
    public void renderAxisLine(Canvas canvas) {
        super.renderAxisLine(canvas);

        float startX = _rectMain.left;
        float startY = _rectMain.bottom;
        float stopX = _rectMain.right;
        float stopY = _rectMain.bottom;

        canvas.drawLine(startX, startY, stopX, stopY, _PaintAxis);
    }


    @Override
    public void renderGridline(Canvas canvas) {
        super.renderGridline(canvas);

        canvas.save();
        canvas.clipRect(_rectMain);// 限制绘制区域

        double[] values = _Axis.getLabelValues();
        int labelCount = _Axis.getLabelCount();
        float x = 0;

        float top = _rectMain.top;
        float bottom = _rectMain.bottom;

        _PathGrid.reset();

        for (int i = 0; i < labelCount; i++) {
            if (values.length < (i + 1)) {
                break;
            }

            double value = values[i];

            SingleF_XY xy = _MappingManager.getPxByValue(value, 0);
            x = xy.getX();

            _PathGrid.moveTo(x, bottom);
            _PathGrid.lineTo(x, top);
        }

        // grid line
        canvas.drawPath(_PathGrid, _PaintGridline);

        canvas.restore();
    }

    @Override
    public void renderLabels(Canvas canvas) {
        super.renderLabels(canvas);

        double[] values = _Axis.getLabelValues();
        int labelCount = _Axis.getLabelCount();

        IValueAdapter adapter = _Axis.get_ValueAdapter();
        float indicator = _Axis.getLeg();

        float x = 0;

        float bottom = _rectMain.bottom;

        for (int i = 0; i < labelCount; i++) {
            if (values.length < (i + 1)) {
                break;
            }

            double value = values[i];
            String label = adapter.value2String(value);

            SingleF_XY xy = _MappingManager.getPxByValue(value, 0);
            x = xy.getX();

            // check
            if (x < _rectMain.left || x > _rectMain.right) {
                continue;
            }

            if (label == null) {
                continue;
            }

            // indicator
            canvas.drawLine(x, bottom, x, bottom + indicator, _PaintLittle);

            // label
            float labelX = x - Utils.textWidth(_PaintLabel, label) / 2;
            float labelY = bottom + _Axis.getLeg() + _Axis.getLabelDimen();
            canvas.drawText(label, labelX, labelY, _PaintLabel);
        }
    }


    @Override
    public void renderUnit(Canvas canvas) {
        super.renderUnit(canvas);

        String unit = _Axis.get_unit();
        Paint paintUnit = _PaintUnit;

        float bottom = _rectMain.bottom;
        float labelX = _rectMain.centerX() - Utils.textWidth(paintUnit, unit) / 2;
        float labelY = bottom + _Axis.getLeg() + _Axis.getLabelDimen() + _Axis.getUnitDimen();

        canvas.drawText(unit, labelX, labelY, _PaintUnit);
    }


    @Override
    public void renderWarnLine(Canvas canvas) {
        super.renderWarnLine(canvas);

        List<WarnLine> warnLines = _Axis.getListWarnLins();
        if (warnLines == null) {
            return;
        }

        canvas.save();
        canvas.clipRect(_rectMain);

        for (WarnLine warnLine : warnLines) {
            if (warnLine.isEnable()) {
                double value = warnLine.getValue();

                SingleF_XY xy = _MappingManager.getPxByValue(value, 0);
                float x = xy.getX();

                if (x < _rectMain.left || x > _rectMain.right) {
                    continue;
                }

                _PaintWarnText.setColor(warnLine.getWarnColor());
                _PaintWarnText.setStrokeWidth(warnLine.getWarnLineWidth());
                _PaintWarnText.setTextSize(warnLine.getTxtSize());

                _PaintWarnPath.setColor(warnLine.getWarnColor());
                _PaintWarnPath.setStrokeWidth(warnLine.getWarnLineWidth());

                _PathWarn.reset();
                _PathWarn.moveTo(x, _rectMain.bottom);
                _PathWarn.lineTo(x, _rectMain.top);

                canvas.drawPath(_PathWarn, _PaintWarnPath);

                float txtHeight = Utils.textHeight(_PaintWarnText);
                float txtWidth = Utils.textWidth(_PaintWarnText, "" + value);

                canvas.drawText(value + "", x - txtWidth * 1.5f, _rectMain.bottom - txtHeight * 1.5f, _PaintWarnText);
            }
        }

        canvas.restore();
    }

} public class YAxisRender extends AxisRender {


    public YAxisRender(RectF _FrameManager, MappingManager _MappingManager, Axis axis) {
        super(_FrameManager, _MappingManager, axis);
    }

    @Override
    public void renderAxisLine(Canvas canvas) {
        super.renderAxisLine(canvas);

        float startX = _rectMain.left;
        float startY = _rectMain.bottom;
        float stopX = _rectMain.left;
        float stopY = _rectMain.top;

        canvas.drawLine(startX, startY, stopX, stopY, _PaintAxis);
    }

    @Override
    public void renderGridline(Canvas canvas) {
        super.renderGridline(canvas);

        canvas.save();
        canvas.clipRect(_rectMain);// 限制绘制区域

        double[] values = _Axis.getLabelValues();
        int labelCount = _Axis.getLabelCount();

        float y = 0;

        float left = _rectMain.left;
        float right = _rectMain.right;

        _PathGrid.reset();

        for (int i = 0; i < labelCount; i++) {
            if (values.length < (i + 1)) {
                break;
            }
            double value = values[i];

            SingleF_XY xy = _MappingManager.getPxByValue(0, value);
            y = xy.getY();

            _PathGrid.moveTo(left, y);
            _PathGrid.lineTo(right, y);
        }

        // grid line
        canvas.drawPath(_PathGrid, _PaintGridline);

        canvas.restore();
    }


    @Override
    public void renderLabels(Canvas canvas) {
        super.renderLabels(canvas);

        IValueAdapter adapter = _Axis.get_ValueAdapter();

        double[] values = _Axis.getLabelValues();
        int labelCount = _Axis.getLabelCount();
        float indicator = _Axis.getLeg();
        float y = 0;

        float left = _rectMain.left;

        int txtHeight = Utils.textHeightAsc(_PaintLabel);

        for (int i = 0; i < labelCount; i++) {
            if (values.length < (i + 1)) {
                break;
            }
            double value = values[i];
            String label = adapter.value2String(value);

            SingleF_XY xy = _MappingManager.getPxByValue(0, value);
            y = xy.getY();

            if (y < _rectMain.top || y > _rectMain.bottom) {
                continue;
            }

            if (label == null) {
                continue;
            }

            // indicator
            canvas.drawLine(left, y, left - indicator, y, _PaintLittle);

            // label
            float labelX = left - _Axis.getLabelDimen() - _Axis.getLeg() * 1.5f;
            float labelY = y + txtHeight / 2;
            canvas.drawText(label, labelX, labelY, _PaintLabel);
        }
    }

    @Override
    public void renderUnit(Canvas canvas) {
        super.renderUnit(canvas);

        float px = _Axis.getUnitDimen();
        float py = _rectMain.centerY() + _Axis.getUnitDimen() / 2;
        canvas.save();
        canvas.rotate(-90, px, py);
        canvas.drawText(_Axis.get_unit(), px, py, _PaintUnit);
        canvas.restore();
    }

    @Override
    public void renderWarnLine(Canvas canvas) {
        super.renderWarnLine(canvas);

        List<WarnLine> warnLines = _Axis.getListWarnLins();
        if (warnLines == null) {
            return;
        }

        canvas.save();
        canvas.clipRect(_rectMain);

        for (WarnLine warnLine : warnLines) {
            if (warnLine.isEnable()) {
                double value = warnLine.getValue();

                SingleF_XY xy = _MappingManager.getPxByValue(0, value);
                float y = xy.getY();

                if (y < _rectMain.top || y > _rectMain.bottom) {
                    continue;
                }

                _PaintWarnText.setColor(warnLine.getWarnColor());
                _PaintWarnText.setStrokeWidth(warnLine.getWarnLineWidth());
                _PaintWarnText.setTextSize(warnLine.getTxtSize());

                _PaintWarnPath.setColor(warnLine.getWarnColor());
                _PaintWarnPath.setStrokeWidth(warnLine.getWarnLineWidth());

                _PathWarn.reset();
                _PathWarn.moveTo(_rectMain.left, y);
                _PathWarn.lineTo(_rectMain.right, y);

                canvas.drawPath(_PathWarn, _PaintWarnPath);

                float txtHeight = Utils.textHeight(_PaintWarnText);
                canvas.drawText(value + "", _rectMain.left + 10, y - txtHeight, _PaintWarnText);
            }
        }

        canvas.restore();
    }
} public class GodTouchListener implements View.OnTouchListener {

    GestureDetector _GestureDetector;
    ScaleGestureDetector _ScaleGestureDetector;
    LineChart _LineChart;

    public GodTouchListener(LineChart lineChart) {
        _LineChart = lineChart;
        _GestureDetector = new GestureDetector(lineChart.getContext(), new GestureListener());
        _ScaleGestureDetector = new ScaleGestureDetector(lineChart.getContext(), new ScaleListener());
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {

        ViewParent parent = v.getParent();
        if (parent != null) {
            parent.requestDisallowInterceptTouchEvent(true);
        }

        boolean hit = _GestureDetector.onTouchEvent(event);
        hit |= _ScaleGestureDetector.onTouchEvent(event);
        if (hit) {
            _LineChart.invalidate();
            return true;
        }

        return false;
    }


    class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {

        float _lastSpanX, _lastSpanY;

        public ScaleListener() {
            super();
        }

        @Override
        public boolean onScale(ScaleGestureDetector detector) {

            float spanX = detector.getCurrentSpanX();
            float kx = spanX / _lastSpanX;

            float spanY = detector.getCurrentSpanY();
            float ky = spanY / _lastSpanY;

            RectF godRect = _LineChart.get_GodRect();
            RectF mainRect = _LineChart.get_MainPlotRect();

            godRect.right = godRect.left + godRect.width() * kx;// 调整左右（宽，胖）
//            godRect.bottom = godRect.top + godRect.height() * ky;

            constrainRect(godRect, mainRect);

            nofityViewPortChanged(godRect);

            _lastSpanX = spanX;
            _lastSpanY = spanY;

            return true;
        }

        @Override
        public boolean onScaleBegin(ScaleGestureDetector detector) {

            _lastSpanX = detector.getCurrentSpanX();
            _lastSpanY = detector.getCurrentSpanY();

            return true;
        }

        @Override
        public void onScaleEnd(ScaleGestureDetector detector) {
            super.onScaleEnd(detector);
        }
    }


    RectD _rectD_ob = new RectD();

    class GestureListener extends GestureDetector.SimpleOnGestureListener {

        @Override
        public boolean onDoubleTap(MotionEvent e) {
            return super.onDoubleTap(e);
        }

        @Override
        public boolean onDown(MotionEvent e) {
            return true;
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            return super.onFling(e1, e2, velocityX, velocityY);
        }

        @Override
        public void onLongPress(MotionEvent e) {
            super.onLongPress(e);
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            changeViewPort(distanceX, distanceY);
            return true;
        }

        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {
            return super.onSingleTapConfirmed(e);
        }
    }


    /**
     * 改变可视的数据范围
     *
     * @param distanceX
     * @param distanceY
     */
    public void changeViewPort(float distanceX, float distanceY) {
        RectF godRect = _LineChart.get_GodRect();
        RectF mainRect = _LineChart.get_MainPlotRect();

        float w = godRect.width();
        float h = godRect.height();
        godRect.left += -distanceX;
        godRect.right = godRect.left + w;
        godRect.top += -distanceY;
        godRect.bottom = godRect.top + h;

        constrainRect(godRect, mainRect);

        nofityViewPortChanged(godRect);
    }

    private void nofityViewPortChanged(RectF godRect) {

        // 计算出godRect对应的viewport
        MappingManager mappingManager = _LineChart.get_MappingManager();
        double left = mappingManager.p2v_x(godRect.left);
        double right = mappingManager.p2v_x(godRect.right);
        double bottom = mappingManager.p2v_y(godRect.bottom);
        double top = mappingManager.p2v_y(godRect.top);

        _rectD_ob.left = left;
        _rectD_ob.top = top;
        _rectD_ob.right = right;
        _rectD_ob.bottom = bottom;
        _LineChart.notifyOB_ViewportChanged(_rectD_ob);
    }


    private void constrainRect(RectF godRect, RectF maxRect) {
        float w = godRect.width();
        float h = godRect.height();

        // 1. 保持比例
        if (godRect.left < maxRect.left) {
            godRect.left = maxRect.left;
            godRect.right = godRect.left + w;
        }
        if (godRect.top < maxRect.top) {
            godRect.top = maxRect.top;
            godRect.bottom = godRect.top + h;
        }
        if (godRect.right > maxRect.right) {
            godRect.right = maxRect.right;
            godRect.left = godRect.right - w;
        }
        if (godRect.bottom > maxRect.bottom) {
            godRect.bottom = maxRect.bottom;
            godRect.top = godRect.bottom - h;
        }

        // 2. 限定值的范围
        if (godRect.left < maxRect.left) {
            godRect.left = maxRect.left;
        }
        if (godRect.top < maxRect.top) {
            godRect.top = maxRect.top;
        }
        if (godRect.right > maxRect.right) {
            godRect.right = maxRect.right;
        }
        if (godRect.bottom > maxRect.bottom) {
            godRect.bottom = maxRect.bottom;
        }
    }

} public class TouchListener implements View.OnTouchListener {

    ViewParent viewParent;

    GestureDetector _GestureDetector;
    Zoomer _Zoomer;
    Scroller _Scroller;

    LineChart _LineChart;
    MappingManager _MappingManager;

    VelocityTracker _VelocityTracker;
    TouchMode _TouchMode = TouchMode.NONE;

    ///////////////////////////////////  平滑的缩放  ///////////////////////////////////
    double _zoom_w, _zoom_h;
    float _zoom_cx, _zoom_cy;

    public TouchListener(LineChart lineChart) {
        this._LineChart = lineChart;

        _GestureDetector = new GestureDetector(_LineChart.getContext(), new GestureListener());
        _MappingManager = lineChart.get_MappingManager();

        _Zoomer = new Zoomer();
        _Scroller = new Scroller(lineChart.getContext());
    }


    float _lastX, _lastY;
    float _disX = 1, _disY = 1, _disXY = 1, _cX, _cY;

    @Override
    public boolean onTouch(View v, MotionEvent event) {

        viewParent = v.getParent();

        // 速度跟踪
        if (_VelocityTracker == null) {
            _VelocityTracker = VelocityTracker.obtain();
        }
        _VelocityTracker.addMovement(event);
        if (event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
            if (_VelocityTracker != null) {
                _VelocityTracker.recycle();
                _VelocityTracker = null;
            }
        }

        boolean hit = _GestureDetector.onTouchEvent(event);
        if (hit) {
            _LineChart.invalidate();
            return true;
        }

        float x = event.getX();
        float y = event.getY();

        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                stopAll();
                iNeedTouch(true);
                _lastX = x;
                _lastY = y;
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                if (event.getPointerCount() >= 2) {
                    _disX = getXDist(event);
                    _disY = getYDist(event);
                    _disXY = getABSDist(event);
                    float tmpX = event.getX(0) + event.getX(1);
                    float tmpY = event.getY(0) + event.getY(1);
                    _cX = (tmpX / 2f);
                    _cY = (tmpY / 2f);

                    _TouchMode = TouchMode.PINCH_ZOOM;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                float dx = x - _lastX;
                float dy = y - _lastY;
                if (_TouchMode == TouchMode.DRAG) {
                    _VelocityTracker.computeCurrentVelocity(1000);
                    float vx = _VelocityTracker.getXVelocity();// 得到当前手指在这一点移动的速度
                    float vy = _VelocityTracker.getYVelocity();// 速度分为了x和y轴两个方向的速度
                    int direction = judgeDir(vx, vy);
                    doDrag(dx, dy, direction);
                } else if (_TouchMode == TouchMode.PINCH_ZOOM) {
                    doPinch(event);
                } else if (_TouchMode == TouchMode.NONE) {
                    _TouchMode = TouchMode.DRAG;
                }

                _lastX = x;
                _lastY = y;
                break;
            case MotionEvent.ACTION_POINTER_UP:
                _TouchMode = TouchMode.NONE;
                break;
            case MotionEvent.ACTION_UP:
                if (_TouchMode == TouchMode.DRAG) {
                    _TouchMode = TouchMode.FLING;

                    _VelocityTracker.computeCurrentVelocity(1000, 5000);
                    int vx = (int) _VelocityTracker.getXVelocity();
                    int vy = (int) _VelocityTracker.getYVelocity();
                    if (vx > 20 || vy > 20) {
                        doFling(event.getX(), event.getY(), vx, vy);
                    }

                    IDragListener listener = _LineChart.get_dragListener();
                    if (listener != null) {
                        listener.onDrag(_LineChart.getVisiableMinX(), _LineChart.getVisiableMaxX());
                    }
                }
                _TouchMode = TouchMode.NONE;
                break;
            case MotionEvent.ACTION_CANCEL:
                _TouchMode = TouchMode.NONE;
                break;
        }

        return true;
    }

    float _lastScrollX, _lastScrollY;

    public void computeScroll() {
        // 考虑滚动
        if (_Scroller.computeScrollOffset()) {

            int currX = _Scroller.getCurrX();
            int currY = _Scroller.getCurrY();

            float dx = currX - _lastScrollX;
            float dy = currY - _lastScrollY;

            doDrag(dx, dy, -1);

            _lastScrollX = currX;
            _lastScrollY = currY;
        }

        // 考虑缩放
        if (_Zoomer.computeZoom()) {

            float currentLevel = _Zoomer.getCurrentZoom();
            zoom(currentLevel, _zoom_w, _zoom_h, _zoom_cx, _zoom_cy);
        }
    }


    boolean hitStart = false;
    int hitCount = 0;

    /**
     * lchart 需要触摸事件
     */
    private void iNeedTouch(boolean need) {

        if (need) {
            viewParent.requestDisallowInterceptTouchEvent(true);
            hitStart = false;
        } else {
            hitCount++;

            if (!hitStart) {
                hitCount = 0;
                hitStart = true;
            } else {

                if (hitCount > 3) {
                    hitStart = false;
                    viewParent.requestDisallowInterceptTouchEvent(false);
                } else {
                    viewParent.requestDisallowInterceptTouchEvent(true);
                }
            }
        }

    }


    private void stopAll() {
        _Scroller.forceFinished(true);
        _Zoomer.stop();
    }


    private int judgeDir(float vx, float vy) {

        float x = Math.abs(vx);
        float y = Math.abs(vy);

        if (x > y) {
            // 方向是水平
            return 1;
        } else {
            // 方向是垂直
            return 2;
        }
    }

    /**
     * 双手缩放
     *
     * @param event
     */
    private void doPinch(MotionEvent event) {

        if (!_LineChart.isScaleable()) {
            return;
        }

        iNeedTouch(true);

        float absDist = getABSDist(event);
        float scale = _disXY / absDist;

        if (zoom_alone) {
            // 考虑独立缩放
            float xDis = getXDist(event);
            float yDis = getYDist(event);

            if (xDis > yDis) {
                zoom(scale, 1, _cX, _cY);
            } else {
                zoom(1, scale, _cX, _cY);
            }
        } else {
            zoom(scale, scale, _cX, _cY);
        }

        _disXY = absDist;
    }

    boolean canX_drag = true;
    boolean canY_drag = true;

    /**
     * 拖拽
     *
     * @param dx
     * @param dy
     */
    private void doDrag(float dx, float dy, int direction) {

        if (!_LineChart.isDragable()) {
            return;
        }

        if (direction != -1) {

            RectD current = _MappingManager.get_currentViewPort();
            RectD maxx = _MappingManager.get_constrainViewPort();

            boolean need = false;

            if (direction == 1) {
                // hor
                need = current.right < maxx.right;
                need &= current.left > maxx.left;

            } else {
                // ver
                need = current.bottom > maxx.bottom;
                need &= current.top < maxx.top;
            }

            iNeedTouch(need);
        }

        if (!canX_drag) {
            dx = 0;
        }
        if (!canY_drag) {
            dy = 0;
        }

        _MappingManager.translate(dx, dy);
        _LineChart.postInvalidate();
    }


    /**
     * 抛掷
     */
    private void doFling(float x, float y, int velocityX, int velocityY) {

        if (!_LineChart.isDragable()) {
            return;
        }

        _Scroller.forceFinished(true);

        _lastScrollX = x;
        _lastScrollY = y;

        _Scroller.fling(
                (int) x, (int) y,
                velocityX, velocityY,
                Integer.MIN_VALUE, Integer.MAX_VALUE,
                Integer.MIN_VALUE, Integer.MAX_VALUE);

        _LineChart.invalidate();
    }


    boolean canX_zoom = true;
    boolean canY_zoom = true;
    boolean zoom_alone = false;

    /**
     * 应用于指定的缩放
     *
     * @param scaleX
     * @param scaleY
     * @param cx
     * @param cy
     */
    private void zoom(float scaleX, float scaleY, float cx, float cy) {

        if (!_LineChart.isScaleable()) {
            return;
        }

        if (!canX_zoom) {
            scaleX = 1;
        }
        if (!canY_zoom) {
            scaleY = 1;
        }

        _MappingManager.zoom(scaleX, scaleY, cx, cy);
        _LineChart.postInvalidate();
    }


    /**
     * 应用于平滑的缩放
     *
     * @param level
     * @param startW
     * @param startH
     * @param cx
     * @param cy
     */
    private void zoom(float level, double startW, double startH, float cx, float cy) {

        if (!_LineChart.isScaleable()) {
            return;
        }

        _MappingManager.zoom(level, startW, startH, cx, cy, canX_zoom, canY_zoom);
        _LineChart.postInvalidate();
    }

    private static float getABSDist(MotionEvent event) {
        float x = event.getX(0) - event.getX(1);
        float y = event.getY(0) - event.getY(1);
        return (float) Math.sqrt(x * x + y * y);
    }

    private static float getXDist(MotionEvent e) {
        float x = Math.abs(e.getX(0) - e.getX(1));
        return x;
    }

    private static float getYDist(MotionEvent e) {
        float y = Math.abs(e.getY(0) - e.getY(1));
        return y;
    }


    public boolean isCanX_drag() {
        return canX_drag;
    }

    public void setCanX_drag(boolean canX_drag) {
        this.canX_drag = canX_drag;
    }

    public boolean isCanY_drag() {
        return canY_drag;
    }

    public void setCanY_drag(boolean canY_drag) {
        this.canY_drag = canY_drag;
    }

    public boolean isCanX_zoom() {
        return canX_zoom;
    }

    public void setCanX_zoom(boolean canX_zoom) {
        this.canX_zoom = canX_zoom;
    }

    public boolean isZoom_alone() {
        return zoom_alone;
    }

    public void setZoom_alone(boolean zoom_alone) {
        this.zoom_alone = zoom_alone;
    }

    public boolean isCanY_zoom() {
        return canY_zoom;
    }

    public void setCanY_zoom(boolean canY_zoom) {
        this.canY_zoom = canY_zoom;
    }

    class GestureListener extends GestureDetector.SimpleOnGestureListener {

        @Override
        public boolean onDoubleTap(MotionEvent e) {

            _Zoomer.stop();

            _zoom_w = _MappingManager.get_currentViewPort().width();
            _zoom_h = _MappingManager.get_currentViewPort().height();

            _zoom_cx = e.getX();
            _zoom_cy = e.getY();

            _Zoomer.startZoom(0.6f);

            return true;
        }

        @Override
        public boolean onDown(MotionEvent e) {
            return super.onDown(e);
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            return super.onFling(e1, e2, velocityX, velocityY);
        }

        @Override
        public void onLongPress(MotionEvent e) {
            super.onLongPress(e);
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            return super.onScroll(e1, e2, distanceX, distanceY);
        }

        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {

            _LineChart.highLight_PixXY(e.getX(), e.getY());
            return true;
        }
    }

    enum TouchMode {
        NONE, DRAG, X_ZOOM, Y_ZOOM, PINCH_ZOOM, ROTATE, SINGLE_TAP, DOUBLE_TAP, LONG_PRESS, FLING
    }
} public class LogUtil {

    private static boolean LOGV = true;
    private static boolean LOGD = true;
    private static boolean LOGI = true;
    private static boolean LOGW = true;
    private static boolean LOGE = true;

    public static void v(String tag, String mess) {
        if (LOGV) {
            Log.v(tag, mess);
        }
    }

    public static void d(String tag, String mess) {
        if (LOGD) {
            Log.d(tag, mess);
        }
    }

    public static void i(String tag, String mess) {
        if (LOGI) {
            Log.i(tag, mess);
        }
    }

    public static void w(String tag, String mess) {
        if (LOGW) {
            Log.w(tag, mess);
        }
    }

    public static void e(String tag, String mess) {
        if (LOGE) {
            Log.e(tag, mess);
        }
    }

    public static void v(String mess) {
        if (LOGV) {
            Log.v(getTag(), mess);
        }
    }

    public static void d(String mess) {
        if (LOGD) {
            Log.d(getTag(), mess);
        }
    }

    public static void i(String mess) {
        if (LOGI) {
            Log.i(getTag(), mess);
        }
    }

    public static void w(String mess) {
        if (LOGW) {
            Log.w(getTag(), mess);
        }
    }

    public static void e(String mess) {
        if (LOGE) {
            Log.e(getTag(), mess);
        }
    }


    private static String getTag() {
        StackTraceElement[] trace = new Throwable().fillInStackTrace()
                .getStackTrace();
        String callingClass = "";

        for (int i = 2; i < trace.length; i++) {
            Class clazz = trace[i].getClass();
            if (!clazz.equals(LogUtil.class)) {
                callingClass = trace[i].getClassName();
                callingClass = callingClass.substring(callingClass
                        .lastIndexOf('.') + 1);
                break;
            }
        }
        return callingClass;
    }
} public class RectD {

    public double left;
    public double top;
    public double right;
    public double bottom;

    public RectD() {
    }

    public RectD(double left, double top, double right, double bottom) {
        this.left = left;
        this.top = top;
        this.right = right;
        this.bottom = bottom;
    }

    public RectD(RectD rectD) {
        setRectD(rectD);
    }

    public void setRectD(RectD rectD) {
        left = rectD.left;
        top = rectD.top;
        right = rectD.right;
        bottom = rectD.bottom;
    }

    public double width() {
        return right - left;
    }

    public double height() {
        return top - bottom;
    }


    public String toString() {
        return "RectD(" + left + ", " + top + ", "
                + right + ", " + bottom + ")";
    }
} public class SingleD_XY {
    private double x;
    private double y;

    private static SingleD_XY value;

    private SingleD_XY() {

    }

    public synchronized static SingleD_XY getInstance() {
        if (value == null) {
            value = new SingleD_XY();
        }
        return value;
    }

    public double getX() {
        return x;
    }

    public SingleD_XY setX(double x) {
        this.x = x;
        return this;
    }

    public double getY() {
        return y;
    }

    public SingleD_XY setY(double y) {
        this.y = y;
        return this;
    }

    @Override
    public String toString() {
        return "U_XY  x: " + x + " y:" + y;
    }
} public class Utils {

    public static float dp2px(float dp) {
        return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
    }
    
    public static float textWidth(Paint paint, String txt) {
        return paint.measureText(txt);
    }

    private static Paint.FontMetrics mFontMetrics = new Paint.FontMetrics();

    public static int textHeightAsc(Paint paint) {
        paint.getFontMetrics(mFontMetrics);
        return (int) (Math.abs(mFontMetrics.ascent));
    }

    public static int textHeight(Paint paint) {
        paint.getFontMetrics(mFontMetrics);
        return (int) (Math.abs(mFontMetrics.ascent) + Math.abs(mFontMetrics.descent));
    }

    /**
     * 将杂乱的数字变成里程碑。
     *
     * @param number
     * @return
     */
    public static float roundNumber2One(double number) {
        if (Double.isInfinite(number) ||
                Double.isNaN(number) ||
                number == 0.0)
            return 0;

        final float d = (float) Math.ceil((float) Math.log10(number < 0 ? -number : number));//有几位？
        final int pw = 1 - (int) d;
        final float magnitude = (float) Math.pow(10, pw);
        final long shifted = Math.round(number * magnitude);
        return shifted / magnitude;
    }


}
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.UiThread;
import android.util.AttributeSet;
import android.view.MotionEvent;
import com.linheimx.app.library.R;
import com.linheimx.app.library.adapter.IValueAdapter;
import com.linheimx.app.library.animate.LAnimator;
import com.linheimx.app.library.data.Entry;
import com.linheimx.app.library.data.Line;
import com.linheimx.app.library.data.Lines;
import com.linheimx.app.library.listener.IDragListener;
import com.linheimx.app.library.manager.MappingManager;
import com.linheimx.app.library.model.HighLight;
import com.linheimx.app.library.model.XAxis;
import com.linheimx.app.library.model.YAxis;
import com.linheimx.app.library.render.GodRender;
import com.linheimx.app.library.render.HighLightRender;
import com.linheimx.app.library.render.LineRender;
import com.linheimx.app.library.render.NoDataRender;
import com.linheimx.app.library.render.XAxisRender;
import com.linheimx.app.library.render.YAxisRender;
import com.linheimx.app.library.touch.GodTouchListener;
import com.linheimx.app.library.touch.TouchListener;
import com.linheimx.app.library.utils.LogUtil;
import com.linheimx.app.library.utils.RectD;
import com.linheimx.app.library.utils.SingleD_XY;
import com.linheimx.app.library.utils.Utils;
import java.util.List;
package com.linheimx.app.library.charts;




/**
 * Created by lijian on 2016/11/13.
 */

public class LineChart extends Chart {

    MappingManager _MappingManager;

    Lines _lines;

    ////////////////////////////  listener  ///////////////////////////
    IDragListener _dragListener;

    ////////////////////////// function //////////////////////////
    boolean isHighLightEnabled = true;
    boolean isTouchEnabled = true;
    boolean isDragable = true;
    boolean isScaleable = true;
    ChartMode _ChartMode;
    CursorMode _CursorMode = CursorMode.cursor1;// 默认使用光标1

    ///////////////////////////////// parts ////////////////////////////////
    XAxis _XAxis;
    YAxis _YAxis;
    HighLight _HighLight1;// 光标1
    HighLight _HighLight2;// 光标2

    //////////////////////////////////  render  /////////////////////////////
    NoDataRender _NoDataRender;