From 93d771ad7fdadb0739b9165614bc63513b7b8690 Mon Sep 17 00:00:00 2001 From: kurkle Date: Tue, 6 Jul 2021 10:12:56 +0300 Subject: [PATCH 1/5] Layout: support box stacking --- docs/axes/cartesian/_common.md | 1 + src/core/core.layouts.js | 76 +++++++++---- test/fixtures/core.layouts/stacked-boxes.js | 106 +++++++++++++++++++ test/fixtures/core.layouts/stacked-boxes.png | Bin 0 -> 17459 bytes 4 files changed, 160 insertions(+), 23 deletions(-) create mode 100644 test/fixtures/core.layouts/stacked-boxes.js create mode 100644 test/fixtures/core.layouts/stacked-boxes.png diff --git a/docs/axes/cartesian/_common.md b/docs/axes/cartesian/_common.md index 96e93a1c9e2..e60e879dd68 100644 --- a/docs/axes/cartesian/_common.md +++ b/docs/axes/cartesian/_common.md @@ -6,6 +6,7 @@ Namespace: `options.scales[scaleId]` | ---- | ---- | ------- | ----------- | `bounds` | `string` | `'ticks'` | Determines the scale bounds. [more...](./index.md#scale-bounds) | `position` | `string` | | Position of the axis. [more...](./index.md#axis-position) +| `stack` | `string` | | Stack group. Axes at the same `position` with same `stack` are stacked. | `axis` | `string` | | Which type of axis this is. Possible values are: `'x'`, `'y'`. If not set, this is inferred from the first character of the ID which should be `'x'` or `'y'`. | `offset` | `boolean` | `false` | If true, extra space is added to the both edges and the axis is scaled to fit into the chart area. This is set to `true` for a bar chart by default. | `title` | `object` | | Scale title configuration. [more...](../labelling.md#scale-title-configuration) diff --git a/src/core/core.layouts.js b/src/core/core.layouts.js index 7206db0d990..0edcb5c16ed 100644 --- a/src/core/core.layouts.js +++ b/src/core/core.layouts.js @@ -37,25 +37,43 @@ function wrapBoxes(boxes) { box, pos: box.position, horizontal: box.isHorizontal(), - weight: box.weight + weight: box.weight, + stack: box.stack }); } return layoutBoxes; } +function buildStacks(layouts) { + const stacks = {}; + for (const wrap of layouts) { + const {stack, pos} = wrap; + if (!stack || !STATIC_POSITIONS.includes(pos)) { + continue; + } + const _stack = stacks[pos + stack] || (stacks[pos + stack] = {count: 0, placed: 0}); + _stack.count++; + } + return stacks; +} + function setLayoutDims(layouts, params) { + const stacks = buildStacks(layouts); let i, ilen, layout; for (i = 0, ilen = layouts.length; i < ilen; ++i) { layout = layouts[i]; // store dimensions used instead of available chartArea in fitBoxes + const stack = stacks[layout.pos + layout.stack]; + const div = stack && stack.count; if (layout.horizontal) { - layout.width = layout.box.fullSize && params.availableWidth; + layout.width = div ? params.vBoxMaxWidth / div : layout.box.fullSize && params.availableWidth; layout.height = params.hBoxMaxHeight; } else { layout.width = params.vBoxMaxWidth; - layout.height = layout.box.fullSize && params.availableHeight; + layout.height = div ? params.hBoxMaxHeight / div : layout.box.fullSize && params.availableHeight; } } + return stacks; } function buildLayoutBoxes(boxes) { @@ -89,7 +107,7 @@ function updateMaxPadding(maxPadding, boxPadding) { maxPadding.right = Math.max(maxPadding.right, boxPadding.right); } -function updateDims(chartArea, params, layout) { +function updateDims(chartArea, params, layout, stacks) { const box = layout.box; const maxPadding = chartArea.maxPadding; @@ -99,7 +117,8 @@ function updateDims(chartArea, params, layout) { // this layout was already counted for, lets first reduce old size chartArea[layout.pos] -= layout.size; } - layout.size = layout.horizontal ? box.height : box.width; + const stack = stacks[layout.pos + layout.stack] || {count: 1}; + layout.size = (layout.horizontal ? box.height : box.width) / stack.count; chartArea[layout.pos] += layout.size; } @@ -150,7 +169,7 @@ function getMargins(horizontal, chartArea) { : marginForPositions(['top', 'bottom']); } -function fitBoxes(boxes, chartArea, params) { +function fitBoxes(boxes, chartArea, params, stacks) { const refitBoxes = []; let i, ilen, layout, box, refit, changed; @@ -163,7 +182,7 @@ function fitBoxes(boxes, chartArea, params) { layout.height || chartArea.h, getMargins(layout.horizontal, chartArea) ); - const {same, other} = updateDims(chartArea, params, layout); + const {same, other} = updateDims(chartArea, params, layout, stacks); // Dimensions changed and there were non full width boxes before this // -> we have to refit those @@ -177,32 +196,41 @@ function fitBoxes(boxes, chartArea, params) { } } - return refit && fitBoxes(refitBoxes, chartArea, params) || changed; + return refit && fitBoxes(refitBoxes, chartArea, params, stacks) || changed; } -function placeBoxes(boxes, chartArea, params) { +function placeBoxes(boxes, chartArea, params, stacks) { const userPadding = params.padding; let x = chartArea.x; let y = chartArea.y; - let i, ilen, layout, box; + let i, ilen, layout, box, stack; for (i = 0, ilen = boxes.length; i < ilen; ++i) { layout = boxes[i]; box = layout.box; + stack = stacks[layout.pos + layout.stack] || {count: 1, placed: 0}; if (layout.horizontal) { - box.left = box.fullSize ? userPadding.left : chartArea.left; - box.right = box.fullSize ? params.outerWidth - userPadding.right : chartArea.left + chartArea.w; + const width = chartArea.w / stack.count; + box.left = box.fullSize ? userPadding.left : chartArea.left + width * stack.placed; + box.right = box.fullSize ? params.outerWidth - userPadding.right : box.left + width; box.top = y; box.bottom = y + box.height; box.width = box.right - box.left; - y = box.bottom; + stack.placed++; + if (stack.placed === stack.count) { + y = box.bottom; + } } else { + const height = chartArea.h / stack.count; box.left = x; box.right = x + box.width; - box.top = box.fullSize ? userPadding.top : chartArea.top; - box.bottom = box.fullSize ? params.outerHeight - userPadding.bottom : chartArea.top + chartArea.h; + box.top = box.fullSize ? userPadding.top : chartArea.top + height * stack.placed; + box.bottom = box.fullSize ? params.outerHeight - userPadding.bottom : box.top + height; box.height = box.bottom - box.top; - x = box.right; + stack.placed++; + if (stack.placed === stack.count) { + x = box.right; + } } } @@ -225,6 +253,7 @@ defaults.set('layout', { * @prop {string} position - The position of the item in the chart layout. Possible values are * 'left', 'top', 'right', 'bottom', and 'chartArea' * @prop {number} weight - The weight used to sort the item. Higher weights are further away from the chart area + * @prop {string} [stack] - The stack group of the boxes in the same position * @prop {boolean} fullSize - if true, and the item is horizontal, then push vertical boxes down * @prop {function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom) * @prop {function} update - Takes two parameters: width and height. Returns size of item @@ -293,6 +322,7 @@ export default { item.fullSize = options.fullSize; item.position = options.position; item.weight = options.weight; + item.stack = options.stack; }, /** @@ -372,30 +402,30 @@ export default { y: padding.top }, padding); - setLayoutDims(verticalBoxes.concat(horizontalBoxes), params); + const stacks = setLayoutDims(verticalBoxes.concat(horizontalBoxes), params); // First fit the fullSize boxes, to reduce probability of re-fitting. - fitBoxes(boxes.fullSize, chartArea, params); + fitBoxes(boxes.fullSize, chartArea, params, stacks); // Then fit vertical boxes - fitBoxes(verticalBoxes, chartArea, params); + fitBoxes(verticalBoxes, chartArea, params, stacks); // Then fit horizontal boxes - if (fitBoxes(horizontalBoxes, chartArea, params)) { + if (fitBoxes(horizontalBoxes, chartArea, params, stacks)) { // if the area changed, re-fit vertical boxes - fitBoxes(verticalBoxes, chartArea, params); + fitBoxes(verticalBoxes, chartArea, params, stacks); } handleMaxPadding(chartArea); // Finally place the boxes to correct coordinates - placeBoxes(boxes.leftAndTop, chartArea, params); + placeBoxes(boxes.leftAndTop, chartArea, params, stacks); // Move to opposite side of chart chartArea.x += chartArea.w; chartArea.y += chartArea.h; - placeBoxes(boxes.rightAndBottom, chartArea, params); + placeBoxes(boxes.rightAndBottom, chartArea, params, stacks); chart.chartArea = { left: chartArea.left, diff --git a/test/fixtures/core.layouts/stacked-boxes.js b/test/fixtures/core.layouts/stacked-boxes.js new file mode 100644 index 00000000000..e4fb345c5fb --- /dev/null +++ b/test/fixtures/core.layouts/stacked-boxes.js @@ -0,0 +1,106 @@ +module.exports = { + config: { + type: 'line', + data: { + datasets: [ + {data: [{x: 1, y: 1}, {x: 2, y: 2}, {x: 3, y: 3}], borderColor: 'red'}, + {data: [{x: 1, y: 1}, {x: 2, y: 2}, {x: 3, y: 3}], yAxisID: 'y1', xAxisID: 'x1', borderColor: 'green'}, + {data: [{x: 1, y: 1}, {x: 2, y: 2}, {x: 3, y: 3}], yAxisID: 'y2', xAxisID: 'x2', borderColor: 'blue'}, + ], + labels: ['tick1', 'tick2', 'tick3'] + }, + options: { + plugins: false, + scales: { + x: { + type: 'linear', + position: 'bottom', + stack: '1', + offset: true, + bounds: 'data', + grid: { + borderColor: 'red' + }, + ticks: { + autoSkip: false, + maxRotation: 0, + count: 3 + } + }, + x1: { + type: 'linear', + position: 'bottom', + stack: '1', + offset: true, + bounds: 'data', + grid: { + borderColor: 'green' + }, + ticks: { + autoSkip: false, + maxRotation: 0, + count: 3 + } + }, + x2: { + type: 'linear', + position: 'bottom', + stack: '1', + offset: true, + bounds: 'data', + grid: { + borderColor: 'blue' + }, + ticks: { + autoSkip: false, + maxRotation: 0, + count: 3 + } + }, + y: { + type: 'linear', + position: 'left', + stack: '1', + offset: true, + grid: { + borderColor: 'red' + }, + ticks: { + precision: 0 + } + }, + y1: { + type: 'linear', + position: 'left', + stack: '1', + offset: true, + grid: { + borderColor: 'green' + }, + ticks: { + precision: 0 + } + }, + y2: { + type: 'linear', + position: 'left', + stack: '1', + offset: true, + grid: { + borderColor: 'blue' + }, + ticks: { + precision: 0 + } + } + } + } + }, + options: { + spriteText: true, + canvas: { + height: 384, + width: 384 + } + } +}; diff --git a/test/fixtures/core.layouts/stacked-boxes.png b/test/fixtures/core.layouts/stacked-boxes.png new file mode 100644 index 0000000000000000000000000000000000000000..e92cea31244740478c475554e90b28837aa5c286 GIT binary patch literal 17459 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 Date: Thu, 8 Jul 2021 09:51:40 +0300 Subject: [PATCH 2/5] Add stackWeight and sample --- docs/.vuepress/config.js | 1 + docs/samples/scales/stacked.md | 71 +++++++++++++++++++++ src/core/core.layouts.js | 113 +++++++++++++++++++-------------- 3 files changed, 138 insertions(+), 47 deletions(-) create mode 100644 docs/samples/scales/stacked.md diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 3a635348fe7..f3867aa5653 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -189,6 +189,7 @@ module.exports = { 'scales/time-line', 'scales/time-max-span', 'scales/time-combo', + 'scales/stacked' ] }, { diff --git a/docs/samples/scales/stacked.md b/docs/samples/scales/stacked.md new file mode 100644 index 00000000000..f6081ab6c04 --- /dev/null +++ b/docs/samples/scales/stacked.md @@ -0,0 +1,71 @@ +# Stacked Linear / Category + +```js chart-editor +// +const DATA_COUNT = 7; +const NUMBER_CFG = {count: DATA_COUNT, min: 0, max: 100}; + +const labels = Utils.months({count: 7}); +const data = { + labels: labels, + datasets: [ + { + label: 'Dataset 1', + data: [10, 30, 50, 20, 25, 44, -10], + borderColor: Utils.CHART_COLORS.red, + backgroundColor: Utils.CHART_COLORS.red, + }, + { + label: 'Dataset 2', + data: ['ON', 'ON', 'OFF', 'ON', 'OFF', 'OFF', 'ON'], + borderColor: Utils.CHART_COLORS.blue, + backgroundColor: Utils.CHART_COLORS.blue, + stepped: true, + yAxisID: 'y2', + } + ] +}; +// + +// +const config = { + type: 'line', + data: data, + options: { + responsive: true, + plugins: { + title: { + display: true, + text: 'Stacked scales', + }, + }, + scales: { + y: { + type: 'linear', + position: 'left', + stack: 'demo', + stackWeight: 2, + grid: { + borderColor: Utils.CHART_COLORS.red + } + }, + y2: { + type: 'category', + labels: ['ON', 'OFF'], + offset: true, + position: 'left', + stack: 'demo', + stackWeight: 1, + grid: { + borderColor: Utils.CHART_COLORS.blue + } + } + } + }, +}; +// + +module.exports = { + config: config, +}; +``` diff --git a/src/core/core.layouts.js b/src/core/core.layouts.js index 0edcb5c16ed..69b876a2fb4 100644 --- a/src/core/core.layouts.js +++ b/src/core/core.layouts.js @@ -1,5 +1,5 @@ import defaults from './core.defaults'; -import {each, isObject} from '../helpers/helpers.core'; +import {defined, each, isObject} from '../helpers/helpers.core'; import {toPadding} from '../helpers/helpers.options'; /** @@ -28,17 +28,19 @@ function sortByWeight(array, reverse) { function wrapBoxes(boxes) { const layoutBoxes = []; - let i, ilen, box; + let i, ilen, box, pos, stack, stackWeight; for (i = 0, ilen = (boxes || []).length; i < ilen; ++i) { box = boxes[i]; + ({position: pos, options: {stack, stackWeight = 1}} = box); layoutBoxes.push({ index: i, box, - pos: box.position, + pos, horizontal: box.isHorizontal(), weight: box.weight, - stack: box.stack + stack: stack && (pos + stack), + stackWeight }); } return layoutBoxes; @@ -47,30 +49,35 @@ function wrapBoxes(boxes) { function buildStacks(layouts) { const stacks = {}; for (const wrap of layouts) { - const {stack, pos} = wrap; + const {stack, pos, stackWeight} = wrap; if (!stack || !STATIC_POSITIONS.includes(pos)) { continue; } - const _stack = stacks[pos + stack] || (stacks[pos + stack] = {count: 0, placed: 0}); + const _stack = stacks[stack] || (stacks[stack] = {count: 0, placed: 0, weight: 0, size: 0}); _stack.count++; + _stack.weight += stackWeight; } return stacks; } +/** + * store dimensions used instead of available chartArea in fitBoxes + **/ function setLayoutDims(layouts, params) { const stacks = buildStacks(layouts); + const {vBoxMaxWidth, hBoxMaxHeight} = params; let i, ilen, layout; for (i = 0, ilen = layouts.length; i < ilen; ++i) { layout = layouts[i]; - // store dimensions used instead of available chartArea in fitBoxes - const stack = stacks[layout.pos + layout.stack]; - const div = stack && stack.count; + const {fullSize} = layout.box; + const stack = stacks[layout.stack]; + const factor = stack && layout.stackWeight / stack.weight; if (layout.horizontal) { - layout.width = div ? params.vBoxMaxWidth / div : layout.box.fullSize && params.availableWidth; - layout.height = params.hBoxMaxHeight; + layout.width = factor ? factor * vBoxMaxWidth : fullSize && params.availableWidth; + layout.height = hBoxMaxHeight; } else { - layout.width = params.vBoxMaxWidth; - layout.height = div ? params.hBoxMaxHeight / div : layout.box.fullSize && params.availableHeight; + layout.width = vBoxMaxWidth; + layout.height = factor ? factor * hBoxMaxHeight : fullSize && params.availableHeight; } } return stacks; @@ -92,7 +99,8 @@ function buildLayoutBoxes(boxes) { rightAndBottom: right.concat(centerVertical).concat(bottom).concat(centerHorizontal), chartArea: filterByPosition(layoutBoxes, 'chartArea'), vertical: left.concat(right).concat(centerVertical), - horizontal: top.concat(bottom).concat(centerHorizontal) + horizontal: top.concat(bottom).concat(centerHorizontal), + all: layoutBoxes }; } @@ -108,18 +116,19 @@ function updateMaxPadding(maxPadding, boxPadding) { } function updateDims(chartArea, params, layout, stacks) { - const box = layout.box; + const {pos, box} = layout; const maxPadding = chartArea.maxPadding; // dynamically placed boxes size is not considered - if (!isObject(layout.pos)) { + if (!isObject(pos)) { if (layout.size) { // this layout was already counted for, lets first reduce old size - chartArea[layout.pos] -= layout.size; + chartArea[pos] -= layout.size; } - const stack = stacks[layout.pos + layout.stack] || {count: 1}; - layout.size = (layout.horizontal ? box.height : box.width) / stack.count; - chartArea[layout.pos] += layout.size; + const stack = stacks[layout.stack] || {size: 0, count: 1}; + stack.size = Math.max(stack.size, layout.horizontal ? box.height : box.width); + layout.size = stack.size / stack.count; + chartArea[pos] += layout.size; } if (box.getPadding) { @@ -199,38 +208,50 @@ function fitBoxes(boxes, chartArea, params, stacks) { return refit && fitBoxes(refitBoxes, chartArea, params, stacks) || changed; } +function setBoxDims(box, left, top, width, height) { + box.top = top; + box.left = left; + box.right = left + width; + box.bottom = top + height; + box.width = width; + box.height = height; +} + function placeBoxes(boxes, chartArea, params, stacks) { const userPadding = params.padding; - let x = chartArea.x; - let y = chartArea.y; - let i, ilen, layout, box, stack; + let {x, y} = chartArea; - for (i = 0, ilen = boxes.length; i < ilen; ++i) { - layout = boxes[i]; - box = layout.box; - stack = stacks[layout.pos + layout.stack] || {count: 1, placed: 0}; + for (const layout of boxes) { + const box = layout.box; + const stack = stacks[layout.stack] || {count: 1, placed: 0, weight: 1}; if (layout.horizontal) { - const width = chartArea.w / stack.count; - box.left = box.fullSize ? userPadding.left : chartArea.left + width * stack.placed; - box.right = box.fullSize ? params.outerWidth - userPadding.right : box.left + width; - box.top = y; - box.bottom = y + box.height; - box.width = box.right - box.left; - stack.placed++; - if (stack.placed === stack.count) { - y = box.bottom; + const width = chartArea.w / stack.weight * layout.stackWeight; + const height = stack.size || box.height; + if (defined(stack.start)) { + y = stack.start; + } + if (box.fullSize) { + setBoxDims(box, userPadding.left, y, params.outerWidth - userPadding.right - userPadding.left, height); + } else { + setBoxDims(box, chartArea.left + stack.placed, y, width, height); } + stack.start = y; + stack.placed += width; + y = box.bottom; } else { - const height = chartArea.h / stack.count; - box.left = x; - box.right = x + box.width; - box.top = box.fullSize ? userPadding.top : chartArea.top + height * stack.placed; - box.bottom = box.fullSize ? params.outerHeight - userPadding.bottom : box.top + height; - box.height = box.bottom - box.top; - stack.placed++; - if (stack.placed === stack.count) { - x = box.right; + const height = chartArea.h / stack.weight * layout.stackWeight; + const width = stack.size || box.width; + if (defined(stack.start)) { + x = stack.start; + } + if (box.fullSize) { + setBoxDims(box, x, userPadding.top, width, params.outerHeight - userPadding.bottom - userPadding.top); + } else { + setBoxDims(box, x, chartArea.top + stack.placed, width, height); } + stack.start = x; + stack.placed += height; + x = box.right; } } @@ -253,7 +274,6 @@ defaults.set('layout', { * @prop {string} position - The position of the item in the chart layout. Possible values are * 'left', 'top', 'right', 'bottom', and 'chartArea' * @prop {number} weight - The weight used to sort the item. Higher weights are further away from the chart area - * @prop {string} [stack] - The stack group of the boxes in the same position * @prop {boolean} fullSize - if true, and the item is horizontal, then push vertical boxes down * @prop {function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom) * @prop {function} update - Takes two parameters: width and height. Returns size of item @@ -322,7 +342,6 @@ export default { item.fullSize = options.fullSize; item.position = options.position; item.weight = options.weight; - item.stack = options.stack; }, /** From f6349657a6e62e76e255733c279e3582acb08b4e Mon Sep 17 00:00:00 2001 From: kurkle Date: Thu, 8 Jul 2021 10:04:17 +0300 Subject: [PATCH 3/5] Cleanup, update docs and types --- docs/axes/cartesian/_common.md | 1 + src/core/core.layouts.js | 3 +-- types/index.esm.d.ts | 12 ++++++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/axes/cartesian/_common.md b/docs/axes/cartesian/_common.md index e60e879dd68..a9082b55fd5 100644 --- a/docs/axes/cartesian/_common.md +++ b/docs/axes/cartesian/_common.md @@ -7,6 +7,7 @@ Namespace: `options.scales[scaleId]` | `bounds` | `string` | `'ticks'` | Determines the scale bounds. [more...](./index.md#scale-bounds) | `position` | `string` | | Position of the axis. [more...](./index.md#axis-position) | `stack` | `string` | | Stack group. Axes at the same `position` with same `stack` are stacked. +| `stackWeight` | `number` | 1 | Weight of the scale in stack group. Used to determine the amount of allocated space for the scale within the group. | `axis` | `string` | | Which type of axis this is. Possible values are: `'x'`, `'y'`. If not set, this is inferred from the first character of the ID which should be `'x'` or `'y'`. | `offset` | `boolean` | `false` | If true, extra space is added to the both edges and the axis is scaled to fit into the chart area. This is set to `true` for a bar chart by default. | `title` | `object` | | Scale title configuration. [more...](../labelling.md#scale-title-configuration) diff --git a/src/core/core.layouts.js b/src/core/core.layouts.js index 69b876a2fb4..e678bacca0b 100644 --- a/src/core/core.layouts.js +++ b/src/core/core.layouts.js @@ -99,8 +99,7 @@ function buildLayoutBoxes(boxes) { rightAndBottom: right.concat(centerVertical).concat(bottom).concat(centerHorizontal), chartArea: filterByPosition(layoutBoxes, 'chartArea'), vertical: left.concat(right).concat(centerVertical), - horizontal: top.concat(bottom).concat(centerHorizontal), - all: layoutBoxes + horizontal: top.concat(bottom).concat(centerHorizontal) }; } diff --git a/types/index.esm.d.ts b/types/index.esm.d.ts index 17c9c02821a..8f95af2529d 100644 --- a/types/index.esm.d.ts +++ b/types/index.esm.d.ts @@ -2829,6 +2829,18 @@ export interface CartesianScaleOptions extends CoreScaleOptions { * Position of the axis. */ position: 'left' | 'top' | 'right' | 'bottom' | 'center' | { [scale: string]: number }; + + /** + * Stack group. Axes at the same `position` with same `stack` are stacked. + */ + stack?: string; + + /** + * Weight of the scale in stack group. Used to determine the amount of allocated space for the scale within the group. + * @default 1 + */ + stackWeight?: number; + /** * Which type of axis this is. Possible values are: 'x', 'y'. If not set, this is inferred from the first character of the ID which should be 'x' or 'y'. */ From d24c0d6d52c70110020d3b6d351aadb6bc7bb892 Mon Sep 17 00:00:00 2001 From: kurkle Date: Fri, 9 Jul 2021 08:40:36 +0300 Subject: [PATCH 4/5] Avoid div0 --- src/core/core.layouts.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/core.layouts.js b/src/core/core.layouts.js index e678bacca0b..bb5c310b616 100644 --- a/src/core/core.layouts.js +++ b/src/core/core.layouts.js @@ -223,8 +223,9 @@ function placeBoxes(boxes, chartArea, params, stacks) { for (const layout of boxes) { const box = layout.box; const stack = stacks[layout.stack] || {count: 1, placed: 0, weight: 1}; + const weight = (stack.weight * layout.stackWeight) || 1 if (layout.horizontal) { - const width = chartArea.w / stack.weight * layout.stackWeight; + const width = chartArea.w / weight; const height = stack.size || box.height; if (defined(stack.start)) { y = stack.start; @@ -238,7 +239,7 @@ function placeBoxes(boxes, chartArea, params, stacks) { stack.placed += width; y = box.bottom; } else { - const height = chartArea.h / stack.weight * layout.stackWeight; + const height = chartArea.h / weight; const width = stack.size || box.width; if (defined(stack.start)) { x = stack.start; From e31272ba61fa99f0d426d22e42613075d69ac84b Mon Sep 17 00:00:00 2001 From: kurkle Date: Fri, 9 Jul 2021 17:58:31 +0300 Subject: [PATCH 5/5] missing semi --- src/core/core.layouts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/core.layouts.js b/src/core/core.layouts.js index bb5c310b616..d5c1e20b3ae 100644 --- a/src/core/core.layouts.js +++ b/src/core/core.layouts.js @@ -223,7 +223,7 @@ function placeBoxes(boxes, chartArea, params, stacks) { for (const layout of boxes) { const box = layout.box; const stack = stacks[layout.stack] || {count: 1, placed: 0, weight: 1}; - const weight = (stack.weight * layout.stackWeight) || 1 + const weight = (stack.weight * layout.stackWeight) || 1; if (layout.horizontal) { const width = chartArea.w / weight; const height = stack.size || box.height;