Giter Club home page Giter Club logo

Comments (4)

jakezatecky avatar jakezatecky commented on August 23, 2024

The area-based calculations are correct for standard funnel views (it's questionable for pyramid views, see #25). Despite this, it does lead to different heights, because subsequent blocks have smaller widths to work with. See the comparisons below for equally weighted blocks that have different bottom widths:

http://jsfiddle.net/3ctsrs60/1/ (equal heights due to non-changing width)
http://jsfiddle.net/okLpk1h9/1/ (different heights due to changing widths)

I agree that this behavior should probably change, because it leads to unintuitive heights and causes its own set of problems with an inverted view. The math calculation is a bit easier when slicing blocks based on proportional areas, but it's not worth the trouble or unintuitive nature. Making height-based calculations would effectively fix #25.

This changes break backwards compatibility, but I was planning on making the v0.7 release break backwards compatibility anyway to standardize some options. Therefore, I think this is appropriate to target this for the v0.7 release.

I am curious to see what modifications you made to the plugin.

from d3-funnel.

arvilsm avatar arvilsm commented on August 23, 2024

For my project, this does the job

key: '_makePaths',
            value: function _makePaths() {
                var paths = [];

                // Initialize velocity
                var dx = this.dx;
                var dy = this.dy;

                // Initialize starting positions
                var prevLeftX = 0;
                var prevRightX = this.width;
                var prevHeight = 0;

                // Start from the bottom for inverted
                if (this.isInverted) {
                    prevLeftX = this.bottomLeftX;
                    prevRightX = this.width - this.bottomLeftX;
                }

                // Initialize next positions
                var nextLeftX = 0;
                var nextRightX = 0;
                var nextHeight = 0;

                var middle = this.width / 2;

                // Move down if there is an initial curve
                if (this.isCurved) {
                    prevHeight = 10;
                }

                var topBase = this.width;
                var bottomBase = 0;

                var totalArea = this.height * (this.width + this.bottomWidth) / 2;
                var slope = 2 * this.height / (this.width - this.bottomWidth);

                // This is greedy in that the block will have a guaranteed height
                // and the remaining is shared among the ratio, instead of being
                // shared according to the remaining minus the guaranteed

                if (this.minHeight !== false) {
                    var height = this.height - this.minHeight * this.data.length;
                    totalArea = height * (this.width + this.bottomWidth) / 2;
                }

                var totalCount = 0;
                var count = 0;

                // Harvest total count
                var sum = 0;
                for (var i = 0; i < this.data.length; i++) {
                    totalCount += isArray(this.data[i][1]) ? this.data[i][1][0] : this.data[i][1];
                    sum += this.data[i][1][0]; //starting edits
                }

                // Create the path definition for each funnel block
                // Remember to loop back to the beginning point for a closed path

                //starting edits
                var exceptionValueCount = 0;

                for (var i = 0; i < this.data.length; i++) {
                    if (this.data[i][1][0] === 0 || Math.round((this.data[i][1][0] * this.height) / sum) <= this.minHeight)
                        exceptionValueCount++;
                }

                sum = (Math.round(sum * 100) / 100);
                //ending edits
                for (var i = 0; i < this.data.length; i++) {
                    count = isArray(this.data[i][1]) ? this.data[i][1][0] : this.data[i][1];

                    // Calculate dynamic shapes based on area
                    if (this.dynamicArea) {
                        var ratio = count / totalCount;
                        var area = ratio * totalArea;

                        if (this.minHeight !== false) {
                            area += this.minHeight * (this.width + this.bottomWidth) / 2;
                        }

                        bottomBase = Math.sqrt((slope * topBase * topBase - 4 * area) / slope);
                        dx = topBase / 2 - bottomBase / 2;
                        //starting edits
                        //dy = area * 2 / (topBase + bottomBase);
                        var y = (sum === 0 || ((this.data[i][1][0] * this.height) / sum) < this.minHeight) ? this.minHeight : (this.data[i][1][0] * (this.height - (exceptionValueCount * this.minHeight))) / sum;

                        dy = y;
                        //ending edits

                        if (this.isCurved) {
                            dy = dy - this.curveHeight / this.data.length;
                        }

                        topBase = bottomBase;
                    }

                    // Stop velocity for pinched blocks
                    if (this.bottomPinch > 0) {
                        // Check if we've reached the bottom of the pinch
                        // If so, stop changing on x
                        if (!this.isInverted) {
                            if (i >= this.data.length - this.bottomPinch) {
                                dx = 0;
                            }
                            // Pinch at the first blocks relating to the bottom pinch
                            // Revert back to normal velocity after pinch
                        } else {
                                // Revert velocity back to the initial if we are using
                                // static area's (prevents zero velocity if isInverted
                                // and bottomPinch are non trivial and dynamicArea is
                                // false)
                                if (!this.dynamicArea) {
                                    dx = this.dx;
                                }

                                dx = i < this.bottomPinch ? 0 : dx;
                            }
                    }

                    // Calculate the position of next block
                    nextLeftX = prevLeftX + dx;
                    nextRightX = prevRightX - dx;
                    nextHeight = prevHeight + dy;

                    // Expand outward if inverted
                    if (this.isInverted) {
                        nextLeftX = prevLeftX - dx;
                        nextRightX = prevRightX + dx;
                    }

                    // Plot curved lines
                    if (this.isCurved) {
                        paths.push([
                        // Top Bezier curve
                        [prevLeftX, prevHeight, 'M'], [middle, prevHeight + (this.curveHeight - 10), 'Q'], [prevRightX, prevHeight, ''],
                        // Right line
                        [nextRightX, nextHeight, 'L'],
                        // Bottom Bezier curve
                        [nextRightX, nextHeight, 'M'], [middle, nextHeight + this.curveHeight, 'Q'], [nextLeftX, nextHeight, ''],
                        // Left line
                        [prevLeftX, prevHeight, 'L']]);
                        // Plot straight lines
                    } else {
                            paths.push([
                            // Start position
                            [prevLeftX, prevHeight, 'M'],
                            // Move to right
                            [prevRightX, prevHeight, 'L'],
                            // Move down
                            [nextRightX, nextHeight, 'L'],
                            // Move to left
                            [nextLeftX, nextHeight, 'L'],
                            // Wrap back to top
                            [prevLeftX, prevHeight, 'L']]);
                        }

                    // Set the next block's previous position
                    prevLeftX = nextLeftX;
                    prevRightX = nextRightX;
                    prevHeight = nextHeight;
                }

                return paths;
            }

from d3-funnel.

jakezatecky avatar jakezatecky commented on August 23, 2024

Significant headway has been made. The dynamic-height branch includes changes for calculating heights directly rather than through trapezoid area.

To-do:

  • Fix broken interactions with isInverted
  • Rename dynamicArea option to a more sensible name

from d3-funnel.

jakezatecky avatar jakezatecky commented on August 23, 2024

Resolved with v0.7.0 release.

from d3-funnel.

Related Issues (20)

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.