Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug] 轴截断系列问题 #3560

Closed
neuqzxy opened this issue Dec 12, 2024 · 4 comments
Closed

[Bug] 轴截断系列问题 #3560

neuqzxy opened this issue Dec 12, 2024 · 4 comments
Assignees
Labels
bug Something isn't working

Comments

@neuqzxy
Copy link
Contributor

neuqzxy commented Dec 12, 2024

Version

1.13.0

Link to Minimal Reproduction

1.13.0

Steps to Reproduce

vstory dsl:

import { useState, useEffect } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
// import './App.css'
import * as VStory from '@visactor/vstory'
import numeral from 'numeral';
import { registerSeriesBreak, appendSeriesBreakConfig } from '@visactor/vchart-extension';

const toolTipSeries = ['24Q3','24Q4'];

const num2Billion = (num: number) => {
  return numeral(num/1000000).format('0.00a');
}

const getChartSpec = (i: number, showLeftAxis: boolean) => {
  const monthList = [['Paid-Ads', 'Preload', 'Sharing', 'SEO', 'Referral', 'Organic&Others'], 
  ['Paid Ads', 'Sharing', 'SEO', 'Orgainc&Others'], 
  ['Push', 'EDM', 'Sharing','SEO'], 
  ['M10n', 'Rec', 'Others']];
  const dataList = [[[844430,907395], [321371,601092], [464004,414381], [144531,168531], [2555,38524], [6086874,7520600]], 
  [[100875, 244903], [280410, 337676], [121273, 160230], [3378803, 3612391]], 
  [[6852093, 7324018], [795727, 990294], [1595700, 1575173],[396048,481796]], 
  [[-1653755, -1713013], [0,183493], [0,-5290]]];
  const data: any[] = [];
  monthList[i].forEach((month, index) => {
    const dl = dataList[i][index];
    dl.forEach((item, i) => {
      data.push({
        month,
        value: item,
        type: toolTipSeries[i]
      });
    })
  });

  return {
    type: 'bar',
    data: {
      values: data
    },
    xField: ['month', 'type'],
    yField: 'value',
    seriesField: 'type',
    label: {
      position: 'outside',
      visible: true,
      overlap: true,
      formatMethod: (text: string) => {
        return numeral(Number(text)/1000000).format('0.00a');
      },
      style: {
        fill: 'black',
      },
    },
    bar:{
      style: {
        fill: datum => datum.type === '24Q3' ? 'rgb(193,211,224)' : 'rgb(72,114,161)'
      }
    },
    axes: [
      {
        orient: 'left',
        grid: {
          visible: false,
        },
        nice: true,
        min: -500000,
        max: 10000000,
        domainLine: {
          visible: true
        },
        breaks:[{
          range: [200000, 6000000]
        }],
        label: {
          visible: true,

          formatMethod: function (text) {
            return (text / 1000000).toFixed(2) +'M';
          }
        },
        tick: {
          visible: true,
          tickStep: 500000,
        },
        visible: showLeftAxis
      },
      {
        orient: 'bottom',
        domainLine: {
          visible: true,
          onZero: true
        },
        tick: {
          visible: false
        },

      }
    ],
    tooltip: {
      style: {
        panel: {
          fontSize: 2,
          /** 背景色 */
          backgroundColor: '#fff',
          /** tooltip边框 */
          border: {
            color: '#6690F2',
            width: 2,
            /** 圆角 */
            radius: 4
          },
          /** tooltip阴影 */
          shadow: {
            blur: 10,
            spread: 2,
            color: '#6690F2'
          }
        }
      },
      dimension: {
        content: {
          key: datum => `${datum.type}`,
          value: datum => numeral(datum.value).format('0,0')
        }
      },
      mark: {
        content: {
          key: datum => `${datum.type}`,
          value: datum => numeral(datum.value).format('0,0')
        }
      },
    }
  };
}

function App() {
  useEffect(() => {
    VStory.registerAll();
    const story = new VStory.Story(null, { dom: 'container', width: 2000, height: 800 , background: '#ebecf0', scaleX: 1, scaleY: 1 });
    const player = new VStory.Player(story);
    story.init(player);
    const leftPadding = 60;
    let left = 0;
    const dataCountList = [6, 4, 4, 3];
    dataCountList.forEach((item, index) => {
      const spec = getChartSpec(index, index === 0);
      registerSeriesBreak();
      appendSeriesBreakConfig(spec);
      console.log(spec);
      story.addCharacter({
        type: 'VChart',
        id: `chart-${index}`,
        zIndex: 9,
        position: {
          x: left + leftPadding,
          y: 250,
          width: item * 100,
          height: 500
        },
        options: {
          padding: { left: 0, top: 0, right: 0, bottom: 0 },
          // panel: {
          //   fill: 'red',
          //   cornerRadius: 10
          // },
          // spec: appendSeriesBreakConfig(getChartSpec(index, index === 0)),
          spec:spec,
          initOption: {
            disableTriggerEvent: false,
            interactive: true
          }
        },

      }, {
        sceneId: 'defaultScene',
        actions: [
          {
            action: 'appear',
            startTime: 1000 * index,
            payload: [
              {
                animation: {
                  duration: 1000,
                  easing: 'linear',
                }
              }
            ]
          }
        ]
      });
      left += item * 100;
    });

    const circleTop = 160;
    [
     [['+279721', '+9.0%'], ['+62965', '+2.0%'], ['-49623', '-1.6%'], ['+35968', '+1.2%'], ['+24000', '+0.8%'], ['+1433726', '+46.1%']],
     [['+144028', '+4.6%'], ['+57266', '+1.8%'], ['+38957', '+1.3%'], ['233588', '+7.5%']],
     [['+471925', '+15.2%'], ['+194568', '+6.3%'], ['-20527', '-0.7%'],['85748','2.8%']], 
     [['-59259', '-1.9%'], ['+183493', '+5.9%'], ['-5290', '-0.2%']]].forEach((item, index) => {
      // let left = (dataCountList[index-1] || 0) * 100;
      let left = dataCountList.slice(0, index).reduce((a, b) => {
        return a + b * 100;
      }, 0);
      let step = 100;
      if (index === 0) {
        left = 40;
        step = (600 - 40) / 6;
      }
      const top = circleTop;
      item.forEach((textList, i) => {
        const text1 = textList[0];
        const textNumber = parseFloat(text1);
        const text2 = textList[1];
        story.addCharacter({
          type: 'Text',
          id: `text1-${index}-${i}`,
          zIndex: 10,
          position: {
            left: left + step * i + step / 2 + leftPadding,
            top,
          },
          options: {
            padding: { left: 10, right: 10, top: 5, bottom: 5 },
            graphic: {
              text: num2Billion(text1),
              fontSize: 13,
              fill: 'black',
              // align: 'center',
              textBaseline: 'middle'
            },
            panel: {
              fill: textNumber > 0 ? (textNumber/1000000 > 0.1 ? 'rgb(90,193,118)' : 'rgb(170,221,182)') : (textNumber/1000000 < -0.1 ? 'rgb(233,184,181)' : 'rgb(242,218,216)'),
              fillOpacity: Math.min(Math.max(Math.abs(textNumber / 20), 2 / 20), 1),
              cornerRadius: 30
            }
          }
        }, {
          sceneId: 'defaultScene',
          actions: [
            {
              action: 'appear',
              startTime: 1000 * index + 1000 / item.length * i,
              payload: [
                {
                  animation: {
                    duration: 1000,
                    easing: 'linear',
                  }
                }
              ]
            }
          ]
        });
        story.addCharacter({
          type: 'Text',
          id: `text2-${index}-${i}`,
          zIndex: 10,
          position: {
            left: left + step * i + step / 2 + leftPadding,
            top: top + 40,
          },
          options: {
            padding: { left: 10, right: 10, top: 5, bottom: 5 },
            graphic: {
              text: text2,
              fontSize: 13,
              fill: textNumber > 0 ? 'green' : 'red',
              fontStyle: 'italic',
              // align: 'center',
              textBaseline: 'middle'
            },
          }
        }, {
          sceneId: 'defaultScene',
          actions: [
            {
              action: 'appear',
              startTime: 1000 * index + 1000 / item.length * i,
              payload: [
                {
                  animation: {
                    duration: 1000,
                    easing: 'linear',
                    effect: 'scale'
                  }
                }
              ]
            }
          ]
        });
      });
    });

    [['New Users + 1.8M', '(57.4%)'], ['Return Users + 0.5M', '(15.2%)'], ['UG Engagement + 0.7M', '(23.5%)'], ['Other Business + 0.1M', '(3.8%)']].forEach((item, index) => {
      const left = dataCountList.slice(0, index).reduce((a, b) => {
        return a + b * 100;
      }, 0);

      story.addCharacter({
        type: 'Text',
        id: `text-title-${index}`,
        zIndex: 10,
        position: {
          left: left + dataCountList[index] * 100 / 2 + leftPadding,
          top: 100,
        },
        options: {
          graphic: {
            text: item,
            fontSize: 13,
            fill: 'black',
            // align: 'center',
            textBaseline: 'middle'
          },
        }
      }, {
        sceneId: 'defaultScene',
        actions: [
          {
            action: 'appear',
            startTime: 1000 * index,
            payload: [
              {
                animation: {
                  duration: 1000,
                  easing: 'linear',
                  effect: 'wipe'
                }
              }
            ]
          }
        ]
      });
    });

    story.addCharacter({
      type: 'Text',
      id: `circleDesc1`,
      zIndex: 10,
      position: {
        left: 0,
        top: circleTop,
      },
      options: {
        graphic: {
          textConfig: [{ text: 'contribution\n', fontWeight: 'bold' }, { text: '(24Q4 vs 24Q3)' }],
          fontSize: 13,
          fill: 'black',
          textAlign: 'left',
          textBaseline: 'middle'
        },
      }
    }, {
      sceneId: 'defaultScene',
      actions: [
        {
          action: 'appear',
          startTime: 1000,
          payload: [
            {
              animation: {
                duration: 1000,
                easing: 'linear',
              }
            }
          ]
        }
      ]
    });
    story.addCharacter({
      type: 'Text',
      id: `circleDesc2`,
      zIndex: 10,
      position: {
        left: 0,
        top: circleTop + 40,
      },
      options: {
        graphic: {
          text: 'contribution%',
          fontSize: 13,
          fill: 'black',
          fontWeight: 'bold',
          textAlign: 'left',
          textBaseline: 'middle'
        },
      }
    }, {
      sceneId: 'defaultScene',
      actions: [
        {
          action: 'appear',
          startTime: 1000,
          payload: [
            {
              animation: {
                duration: 1000,
                easing: 'linear',
              }
            }
          ]
        }
      ]
    });
    story.addCharacter({
      type: 'Text',
      id: `title`,
      zIndex: 10,
      position: {
        left: 20,
        top: 20,
      },
      options: {
        graphic: {
          text: 'US DAU Increment Attribution (Q3 110.5M→ Q4 113.4M, +2.9M)',
          fontSize: 22,
          fill: 'black',
          textAlign: 'left',
          fontWeight: 'bold',
          textBaseline: 'top'
        },
      }
    }, {
      sceneId: 'defaultScene',
      actions: [
        {
          action: 'appear',
          startTime: 1000,
          payload: [
            {
              animation: {
                duration: 2000,
                easing: 'linear',
                effect: 'typewriter'
              }
            }
          ]
        }
      ]
    });

    player.play(-1);
    return () => {
      story.release();
    }
  }, []);

  return (
    <>
      <div id="container"></div>
    </>
  )
}

export default App

Current Behavior

  1. 配置了step后,截断的轴tick没有被删除
const spec = {
    "type": "bar",
    "data": {
        "values": [
            {
                "month": "Paid-Ads",
                "value": 844430,
                "type": "24Q3"
            },
            {
                "month": "Paid-Ads",
                "value": 907395,
                "type": "24Q4"
            },
            {
                "month": "Preload",
                "value": 321371,
                "type": "24Q3"
            },
            {
                "month": "Preload",
                "value": 601092,
                "type": "24Q4"
            },
            {
                "month": "Sharing",
                "value": 464004,
                "type": "24Q3"
            },
            {
                "month": "Sharing",
                "value": 414381,
                "type": "24Q4"
            },
            {
                "month": "SEO",
                "value": 144531,
                "type": "24Q3"
            },
            {
                "month": "SEO",
                "value": 168531,
                "type": "24Q4"
            },
            {
                "month": "Referral",
                "value": 2555,
                "type": "24Q3"
            },
            {
                "month": "Referral",
                "value": 38524,
                "type": "24Q4"
            },
            {
                "month": "Organic&Others",
                "value": 6086874,
                "type": "24Q3"
            },
            {
                "month": "Organic&Others",
                "value": 7520600,
                "type": "24Q4"
            }
        ]
    },
    "xField": [
        "month",
        "type"
    ],
    "yField": "value",
    "seriesField": "type",
    "label": {
        "position": "outside",
        "visible": true,
        "overlap": true,
        "style": {
            "fill": "black"
        }
    },
    "bar": {
        "style": {}
    },
    "axes": [
        {
            "orient": "left",
            "grid": {
                "visible": false
            },
            "nice": true,
            "min": -500000,
            "max": 10000000,
            "domainLine": {
                "visible": true
            },
            "breaks": [
                {
                    "range": [
                        200000,
                        6000000
                    ]
                }
            ],
            "label": {
                "visible": true
            },
            "tick": {
                "visible": true,
                // "tickStep": 500000
            },
            "visible": true
        },
        {
            "orient": "bottom",
            "domainLine": {
                "visible": true,
                "onZero": true
            },
            "tick": {
                "visible": false
            }
        }
    ],
    "tooltip": {
        "style": {
            "panel": {
                "fontSize": 2,
                "backgroundColor": "#fff",
                "border": {
                    "color": "#6690F2",
                    "width": 2,
                    "radius": 4
                },
                "shadow": {
                    "blur": 10,
                    "spread": 2,
                    "color": "#6690F2"
                }
            }
        },
        "dimension": {
            "content": {}
        },
        "mark": {
            "content": {}
        }
    },
    "customMark": [
        {
            "type": "component",
            "componentType": "seriesBreak",
            "interactive": true,
            "zIndex": 500,
            "style": {}
        }
    ],
    "padding": {
        "left": 0,
        "top": 0,
        "right": 0,
        "bottom": 0
    },
    "animation": true,
    "animationAppear": false
};

const vchart = new VChart(spec, { dom: CONTAINER_ID });
vchart.renderSync();

// Just for the convenience of console debugging, DO NOT COPY!
window['vchart'] = vchart;

image
image
2. 轴截断没有考虑轴scale的min和max,只对数据的区间做判定
image

{
    "type": "bar",
    "data": {
        "values": [
            {
                "month": "Paid Ads",
                "value": 100875,
                "type": "24Q3"
            },
            {
                "month": "Paid Ads",
                "value": 244903,
                "type": "24Q4"
            },
            {
                "month": "Sharing",
                "value": 280410,
                "type": "24Q3"
            },
            {
                "month": "Sharing",
                "value": 337676,
                "type": "24Q4"
            },
            {
                "month": "SEO",
                "value": 121273,
                "type": "24Q3"
            },
            {
                "month": "SEO",
                "value": 160230,
                "type": "24Q4"
            },
            {
                "month": "Orgainc&Others",
                "value": 3378803,
                "type": "24Q3"
            },
            {
                "month": "Orgainc&Others",
                "value": 3612391,
                "type": "24Q4"
            }
        ]
    },
    "xField": [
        "month",
        "type"
    ],
    "yField": "value",
    "seriesField": "type",
    "label": {
        "position": "outside",
        "visible": true,
        "overlap": true,
        "style": {
            "fill": "black"
        }
    },
    "bar": {
        "style": {}
    },
    "axes": [
        {
            "orient": "left",
            "grid": {
                "visible": false
            },
            "nice": true,
            "min": -500000,
            "max": 10000000,
            "domainLine": {
                "visible": true
            },
            "breaks": [
                {
                    "range": [
                        200000,
                        6000000
                    ]
                }
            ],
            "label": {
                "visible": true
            },
            "tick": {
                "visible": true,
                "tickStep": 500000
            },
            "visible": false
        },
        {
            "orient": "bottom",
            "domainLine": {
                "visible": true,
                "onZero": true
            },
            "tick": {
                "visible": false
            }
        }
    ],
    "tooltip": {
        "style": {
            "panel": {
                "fontSize": 2,
                "backgroundColor": "#fff",
                "border": {
                    "color": "#6690F2",
                    "width": 2,
                    "radius": 4
                },
                "shadow": {
                    "blur": 10,
                    "spread": 2,
                    "color": "#6690F2"
                }
            }
        },
        "dimension": {
            "content": {}
        },
        "mark": {
            "content": {}
        }
    },
    "customMark": [
        {
            "type": "component",
            "componentType": "seriesBreak",
            "interactive": true,
            "zIndex": 500,
            "style": {}
        }
    ],
    "padding": {
        "left": 0,
        "top": 0,
        "right": 0,
        "bottom": 0
    },
    "animation": true,
    "animationAppear": false
}
  1. 轴截断和系列截断希望保持一致

Expected Behavior

  1. 轴截断考虑手动配置的min和max
  2. 配置了step后,轴tick需要删除

Environment

- OS:
- Browser:
- Framework:

Any additional comments?

No response

@neuqzxy neuqzxy added the bug Something isn't working label Dec 12, 2024
@xile611 xile611 self-assigned this Dec 13, 2024
@neuqzxy
Copy link
Contributor Author

neuqzxy commented Dec 13, 2024

比如数据的范围是0-100,用户配置了轴截断是200-300,那么轴截断是不生效的。但它忽略了一种情况是,虽然数据是0-100,但用户给轴配置了min和max是0-500。这个时候轴截断依然要生效

@neuqzxy
Copy link
Contributor Author

neuqzxy commented Jan 8, 2025

@xile611 轴截断在轴的min/max区间,但不在数据真实区间的时候,存在问题

const spec = {
    "type": "bar",
    "data": {
        "values": [
            {
                "month": "Push",
                "value": 71.9,
                "type": "Attraction 1"
            },
            {
                "month": "Push",
                "value": 85.6,
                "type": "Attraction 2"
            },
            {
                "month": "Sharing",
                "value": 10.8,
                "type": "Attraction 1"
            },
            {
                "month": "Sharing",
                "value": 11.9,
                "type": "Attraction 2"
            },
            {
                "month": "SEO",
                "value": 1.3,
                "type": "Attraction 1"
            },
            {
                "month": "SEO",
                "value": 2.2,
                "type": "Attraction 2"
            },
            {
                "month": "Incentive",
                "value": 4.2,
                "type": "Attraction 1"
            },
            {
                "month": "Incentive",
                "value": 3.6,
                "type": "Attraction 2"
            },
            {
                "month": "EDM",
                "value": 1.5,
                "type": "Attraction 1"
            },
            {
                "month": "EDM",
                "value": 1.1,
                "type": "Attraction 2"
            }
        ]
    },
    "xField": [
        "month",
        "type"
    ],
    "yField": "value",
    "seriesField": "type",
    "label": {
        "visible": true,
        "position": "top",
        "overlap": false
    },
    "axes": [
        {
            "orient": "left",
            "grid": {
                "visible": false
            },
            "nice": false,
            "min": -20,
            "max": 210,
            "breaks": [
                {
                    "range": [
                        100,
                        200 
                    ]
                }
            ],
            "domainLine": {
                "visible": true
            },
            "tick": {
                "visible": true,
                "tickStep": 10
            },
            "visible": true
        },
        {
            "orient": "bottom",
            "domainLine": {
                "visible": true,
                "onZero": true
            },
            "tick": {
                "visible": false
            }
        }
    ]
};

const vchart = new VChart(spec, { dom: CONTAINER_ID });
vchart.renderSync();

// Just for the convenience of console debugging, DO NOT COPY!
window['vchart'] = vchart;

image

@neuqzxy neuqzxy reopened this Jan 8, 2025
@neuqzxy
Copy link
Contributor Author

neuqzxy commented Jan 8, 2025

也可以在vstory的例子中进行复现:
https://visactor.com/vstory/demo/works-show/chart-join

@xile611
Copy link
Contributor

xile611 commented Jan 17, 2025

fixed in 1.13.4

@xile611 xile611 closed this as completed Jan 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants