【问题标题】:Calculating a marimekko/stacked chart in javascript在 javascript 中计算 marimekko/堆积图
【发布时间】:2021-05-11 23:08:21
【问题描述】:

我的输入数据具有不同数量的数据项和匹配数量的值项。我知道那里已经有图书馆,但我正在创建自己的图书馆;)

因此,如果有 3 列,则每列有 3 个值(就像 marimekko 图表一样):

我有以下数据虚拟输入,但它们的范围可以是任何正值:

const json = `{ "data": [ 
  { "value": [ 1,  2,  3,  4] }, 
  { "value": [ 5,  6,  7,  8] },
  { "value": [ 9, 10, 11, 12] },
  { "value": [13, 14, 15, 16] } 
] }`

有了它,我得到了以下计算每列的总数(所以,[ 1, 5, 9, 13 ], [ 2, 6, 10, 14 ] ... etc:

const stackIndexTotal = (
  dataJSON.slice(1).reduce( ( (sums, { value } ) =>
    sums.map( ( sum, i ) => sum + value[ i ] )
  ), dataJSON[0].value )
);

目前我有这个代码和输出:

// dummy data
const json = `{ "data": [ 
  { "value": [ 1,  2,  3,  4] }, 
  { "value": [ 5,  6,  7,  8] },
  { "value": [ 9, 10, 11, 12] },
  { "value": [13, 14, 15, 16] } 
] }`,
  dataJSON = JSON.parse(json).data;

// find the sum of all the values at same index
const stackIndexTotal = (
  dataJSON.slice(1).reduce(((sums, {
      value
    }) =>
    sums.map((sum, i) => sum + value[i])
  ), dataJSON[0].value)
);


// loop the data
dataJSON.forEach((data, index) => {

  // variables
  const dataContainer = document.getElementById('data-container');
  const dataItem = document.createElementNS('http://www.w3.org/2000/svg', 'g');

  // inside the data:{}
  const numberOfValues = dataJSON.length;

  // loop the value array
  data.value.forEach((item, i) => {

    // inside the value:[]
    const numberOfValueInArray = data.value.length;

    const rectHeight = (item / stackIndexTotal[index]) * 100;

    // 100 width view, divided by number of groups
    const widthOfSections = (100 / numberOfValues);

    // add in text items
    dataItem.innerHTML += (

      // the rectangle svg
      // -- fill: dummy colours - each index, or same column has same colour
      // -- x: offset of the xPosition
      // -- width: fixed width for demo
      // -- height: height of rect is percentage of arrays column total
      // -- y: need to calculate offset

      `<rect
        fill="hsl(20, 30%, ${ ((100 / numberOfValues) * index) }%)"
        x="${ (widthOfSections * i ) }"
        width="${ widthOfSections }"
        height="${ rectHeight }"

        y="0"
       ></rect>`
    );

  });

  // add into the DOM
  dataContainer.appendChild(dataItem);

});
svg {
  max-width: 25em;
  max-height: 25em;
  border: 2px solid;
  margin: 2em 5em;
  overflow: visible !important;
}
&lt;svg viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet"&gt;&lt;g id="data-container"&gt;&lt;/g&gt;&lt;/svg&gt;

但希望得到以下输出。

这样,一旦我可以得到正确的偏移量,我就可以将所有值设为Math.max(...stackIndexTotal) 的百分比。

目前,我在两个循环中尝试的任何事情似乎都没有正确偏移块。我尝试获取以前的值:data.value[ i - 1 ],然后使用该百分比作为起点,但这似乎行不通。我尝试使用第一个循环index 来制作所有index === 0 ? 0,因此它们都从顶部开始,但是之后每个项目的偏移量似乎都关闭了。

【问题讨论】:

    标签: javascript html css math svg


    【解决方案1】:

    我希望这是你的要求。

    使用数据我正在计算要绘制的矩形的位置 x、y 和大小(宽度和高度)。请阅读代码中的cmets。

    const SVG_NS = "http://www.w3.org/2000/svg";
    let container = document.querySelector("#data-container");
    
    const json_ry = {
      data: [
        { value: [1, 2, 3, 4] },
        { value: [5, 6, 7, 8] },
        { value: [9, 10, 11, 12] },
        { value: [13, 14, 15, 16] },
        { value: [17, 18, 19, 20] }
      ]
    };
    
    let lngth = json_ry.data.length;
    
    
    // creating an array with the heights of the rectangles
    let heights = new Array(lngth);
    heights.fill([]);
    
    for (let i = 0; i < heights.length; i++) {
      heights[i] = json_ry.data[i].value
    }
    
    
    // create an array with the y positions of the rects 
    let positions = [];
    //by copying the heights array  
    
    for (let i = 0; i < heights.length; i++) {
      positions[i] = [];
      for (let j = 0; j < heights[i].length; j++) {
        positions[i][j] = heights[i][j];
      }
    }
    //then calculate the y position as a sum of the previous position and the previous height
    positions[0] = [0, 0, 0, 0];
    for (let i = 1; i < positions.length; i++) {
      for (let j = 0; j < positions[i].length; j++) {
        positions[i][j] = positions[i - 1][j] + heights[i - 1][j];
      }
    }
    
    //console.log("heights",heights)
    //console.log("positions",positions)
    
    for (let i = 0; i < heights.length; i++) {
      for (let j = 0; j < heights[i].length; j++) {
      //build an object with attributes for the rect
        let o = {
          x: j * 25,
          y: positions[i][j],
          width: 25,
          height: heights[i][j],
          fill: `hsl(0,30%,${(i / lngth) * 100}%)`
        };
        //create a new rectangle
        drawSVGelmt(o, "rect", container);
      }
    }
    
    
    // a function to create and draw an svg element
    function drawSVGelmt(o, tag, parent) {
      let elmt = document.createElementNS(SVG_NS, tag);
      for (let name in o) {
        if (o.hasOwnProperty(name)) {
          elmt.setAttributeNS(null, name, o[name]);
        }
      }
      parent.appendChild(elmt);
      return elmt;
    }
    &lt;svg viewBox="0 0 100 100"&gt;&lt;g id="data-container"&gt;&lt;/g&gt;&lt;/svg&gt;

    【讨论】:

    • 谢谢!你在正确的轨道上 100%,但它似乎没有扩展?并且每个数组都在值数组中匹配
    【解决方案2】:

    对于想要创建这些图表之一的未来人 - 并经历同样​​的斗争。 @enxaneta 有一个很好的解决方案,但它并没有超出固定值。

    对于我的解决方案:

    // dummy data
    const json = `{ "data": [ 
      { "value": [ 1,  2,  3,  4] }, 
      { "value": [ 5,  6,  7,  8] },
      { "value": [ 9, 10, 11, 12] },
      { "value": [13, 14, 15, 16] } 
    ] }`,
      dataJSON = JSON.parse(json).data;
    
    // rework the data
    // -- get the columns
    // -- then the sum of the columns
    const dataJSONColumn = dataJSON.map((current, index, arr) => {
        // create the blanks
        let outputArray = [],
          previousTotal = 0;
    
        // loop through
        for (let i = 0; i < current.value.length; i++) {
          // if it is first item
          if (i < 1) {
            // set the value to 0
            previousTotal = 0;
          } else {
            // add from the last value
            previousTotal += arr[i - 1].value[index];
          }
    
          // output the new array
          outputArray.push(previousTotal);
        }
    
        return outputArray;
      }),
      // get the sum of all vertical values
      stackIndexTotal = dataJSON
      .slice(1)
      .reduce(
        (sums, {
          value
        }) => sums.map((sum, i) => sum + value[i]),
        dataJSON[0].value
      ),
      // number of dataItems
      numberOfDataItems = dataJSON.length;
    
    // begin the loop
    dataJSON.forEach((data, index) => {
      // variables
      const dataContainer = document.getElementById("data-container");
    
      // cache wrapper
      const dataValue = Array.isArray(data.value) ? data.value : [data.value],
        // number of [value] points
        numberInValueArray = dataValue.length,
        // -- size of the group sections
        widthOfColumn = 100 / numberOfDataItems,
        // sum of new array
        dataJSONColumnTotal = dataJSONColumn[index][dataJSONColumn[index].length - 1],
        // create the data-item
        dataItem = document.createElementNS("http://www.w3.org/2000/svg", "g");
    
      // loop the values
      dataValue.forEach((item, i) => {
        // how tall is the bar in the stack
        const stackDataItemPercent = (item / stackIndexTotal[i]) * 100,
          // how far to offset it
          stackDataItemOffset = (dataJSONColumn[i][index] / stackIndexTotal[i]) * 100;
    
        // bar item value
        dataItem.innerHTML += `
                                        <rect
                                            width="${widthOfColumn}"
                                            height="${stackDataItemPercent}"
                                            fill="hsl(120, 30%, ${ ((100 / numberInValueArray) * index) }%)"
                                            x="${widthOfColumn * i}"
                                            y="${stackDataItemOffset}"
                                        />
                                    `;
      });
    
      // add into the DOM
      dataContainer.appendChild(dataItem);
    });
    svg {
      max-width: 25em;
      margin: 2em 5em;
      overflow: visible !important;
    }
    &lt;svg viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet"&gt;&lt;g id="data-container"&gt;&lt;/g&gt;&lt;/svg&gt;

    【讨论】:

      猜你喜欢
      • 2011-08-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-16
      • 2021-03-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多