Skip to content

Commit bbd43a5

Browse files
authored
Merge pull request #58 from vue-pivottable/plotly-renderer
feat: Plotly 차트 렌더러 추가 #57
2 parents dffb03e + 823e5d6 commit bbd43a5

File tree

14 files changed

+545
-4336
lines changed

14 files changed

+545
-4336
lines changed

package-lock.json

-4,335
This file was deleted.

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@
5858
"vue-draggable-next": "^2.2.1"
5959
},
6060
"devDependencies": {
61+
"@vue-pivottable/lazy-table-renderer": "workspace:*",
62+
"@vue-pivottable/plotly-renderer": "workspace:*",
6163
"@eslint/js": "^9.21.0",
6264
"@vitejs/plugin-vue": "^5.2.1",
6365
"@vue-pivottable/lazy-table-renderer": "workspace:*",

packages/plotly-renderer/LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 vue-pivottable
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

packages/plotly-renderer/README.md

Whitespace-only changes.

packages/plotly-renderer/package.json

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"name": "@vue-pivottable/plotly-renderer",
3+
"version": "1.0.0",
4+
"type": "module",
5+
"exports": {
6+
".": {
7+
"import": "./dist/plotly-renderer.es.js",
8+
"require": "./dist/plotly-renderer.umd.js"
9+
},
10+
"./dist/plotly-renderer.css": "./dist/plotly-renderer.css"
11+
},
12+
"main": "./dist/plotly-renderer.umd.js",
13+
"module": "./dist/plotly-renderer.es.js",
14+
"files": [
15+
"dist"
16+
],
17+
"repository": {
18+
"type": "git",
19+
"url": "git+https://github.com/vue-pivottable/vue3-pivottable.git",
20+
"directory": "packages/plotly-renderer"
21+
},
22+
"keywords": [
23+
"vue",
24+
"vue3",
25+
"pivot",
26+
"pivottable",
27+
"vue-pivottable",
28+
"vue3-pivottable",
29+
"@vue-pivottable/plotly-renderer"
30+
],
31+
"homepage": "https://github.com/vue-pivottable/vue3-pivottable/tree/main/packages/plotly-renderer",
32+
"author": "Sumin, Lee <[email protected]>",
33+
"license": "MIT",
34+
"scripts": {
35+
"dev": "vite",
36+
"build": "vite build",
37+
"preview": "vite preview"
38+
},
39+
"dependencies": {
40+
"@clalarco/vue3-plotly": "^0.1.5",
41+
"plotly.js-basic-dist": "^3.0.1"
42+
},
43+
"peerDependencies": {
44+
"vue": "^3.2.0",
45+
"vue-pivottable": "^1.0.0-alpha.0"
46+
},
47+
"devDependencies": {
48+
"vue-pivottable": "workspace:*",
49+
"@vitejs/plugin-vue": "^5.2.1",
50+
"vite": "^6.1.0"
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<template>
2+
<VuePlotly
3+
:data="chartData"
4+
:layout="layout"
5+
/>
6+
</template>
7+
8+
<script setup>
9+
import { VuePlotly } from '@clalarco/vue3-plotly'
10+
import { useProvidePivotData } from 'vue-pivottable'
11+
import { useChartData } from '../composables/useChartData'
12+
13+
const props = defineProps({
14+
traceOptions: {
15+
type: Object,
16+
required: true
17+
},
18+
layoutOptions: {
19+
type: Object,
20+
required: true
21+
},
22+
transpose: {
23+
type: Boolean,
24+
required: true
25+
},
26+
aggregatorName: {
27+
type: String,
28+
required: true
29+
},
30+
aggregators: {
31+
type: Object,
32+
required: true
33+
},
34+
rows: {
35+
type: Array,
36+
required: true
37+
},
38+
cols: {
39+
type: Array,
40+
required: true
41+
},
42+
vals: {
43+
type: Array,
44+
required: true
45+
}
46+
})
47+
48+
const { rowKeys, colKeys, getAggregator } = useProvidePivotData()
49+
50+
const { chartData, layout } = useChartData(
51+
props,
52+
rowKeys,
53+
colKeys,
54+
getAggregator
55+
)
56+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<template>
2+
<VuePlotly
3+
:data="chartData"
4+
:layout="layout"
5+
/>
6+
</template>
7+
8+
<script setup>
9+
import { VuePlotly } from '@clalarco/vue3-plotly'
10+
import { useProvidePivotData } from 'vue-pivottable'
11+
import { useScatterChartData } from '../composables/useScatterChartData'
12+
13+
const props = defineProps({
14+
traceOptions: {
15+
type: Object,
16+
required: true
17+
},
18+
layoutOptions: {
19+
type: Object,
20+
required: true
21+
},
22+
transpose: {
23+
type: Boolean,
24+
required: true
25+
},
26+
aggregatorName: {
27+
type: String,
28+
required: true
29+
},
30+
aggregators: {
31+
type: Object,
32+
required: true
33+
},
34+
rows: {
35+
type: Array,
36+
required: true
37+
},
38+
cols: {
39+
type: Array,
40+
required: true
41+
},
42+
vals: {
43+
type: Array,
44+
required: true
45+
}
46+
})
47+
48+
const { rowKeys, colKeys, getAggregator } = useProvidePivotData()
49+
50+
const { chartData, layout } = useScatterChartData(
51+
props,
52+
rowKeys,
53+
colKeys,
54+
getAggregator
55+
)
56+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { computed } from 'vue'
2+
3+
export function useChartData(props, rowKeys, colKeys, getAggregator) {
4+
const traceKeys = computed(() => {
5+
const keys = props.transpose ? colKeys.value : rowKeys.value
6+
return keys.length > 0 ? keys : [[]]
7+
})
8+
const datumKeys = computed(() => {
9+
const keys = props.transpose ? rowKeys.value : colKeys.value
10+
return keys.length > 0 ? keys : [[]]
11+
})
12+
13+
const fullAggName = computed(() => {
14+
const baseName = props.aggregatorName
15+
const numInputs = props.aggregators[baseName]([])().numInputs || 0
16+
if (numInputs !== 0) {
17+
const valsSlice = props.vals.slice(0, numInputs).join(', ')
18+
return `${baseName} of ${valsSlice}`
19+
}
20+
21+
return baseName
22+
})
23+
24+
const chartData = computed(() =>
25+
traceKeys.value.map((traceKey) => {
26+
const values = []
27+
const labels = []
28+
for (const datumKey of datumKeys.value) {
29+
const val = parseFloat(
30+
getAggregator(
31+
props.transpose ? datumKey : traceKey,
32+
props.transpose ? traceKey : datumKey
33+
).value()
34+
)
35+
values.push(isFinite(val) ? val : null)
36+
labels.push(datumKey.join('-') || ' ')
37+
}
38+
39+
const trace = {
40+
name: traceKey.join('-') || fullAggName.value,
41+
...props.traceOptions
42+
}
43+
44+
if (props.traceOptions.type === 'pie') {
45+
trace.values = values
46+
trace.labels = labels.length > 1 ? labels : [fullAggName.value]
47+
} else {
48+
trace.x = props.transpose ? values : labels
49+
trace.y = props.transpose ? labels : values
50+
}
51+
52+
return trace
53+
})
54+
)
55+
56+
const titleText = computed(() => {
57+
let title = fullAggName.value
58+
59+
const hAxisTitle = props.transpose.value
60+
? props.rows.join('-')
61+
: props.cols.join('-')
62+
63+
const groupByTitle = props.transpose.value
64+
? props.cols.join('-')
65+
: props.rows.join('-')
66+
67+
if (hAxisTitle !== '') title += ` vs ${hAxisTitle}`
68+
if (groupByTitle !== '') title += ` by ${groupByTitle}`
69+
70+
return title
71+
})
72+
73+
const layout = computed(() => {
74+
const baseLayout = {
75+
title: titleText.value,
76+
hovermode: 'closest',
77+
width: window.innerWidth / 1.5,
78+
height: window.innerHeight / 1.4 - 50
79+
}
80+
81+
if (props.traceOptions.type === 'pie') {
82+
const columns = Math.ceil(Math.sqrt(chartData.value.length))
83+
const rows = Math.ceil(chartData.value.length / columns)
84+
baseLayout.grid = { columns, rows }
85+
86+
chartData.value.forEach((d, i) => {
87+
d.domain = {
88+
row: Math.floor(i / columns),
89+
column: i - columns * Math.floor(i / columns)
90+
}
91+
if (chartData.value.length > 1) d.title = d.name
92+
})
93+
94+
if (chartData.value[0].labels?.length === 1) {
95+
baseLayout.showlegend = false
96+
}
97+
} else {
98+
baseLayout.xaxis = {
99+
title: props.transpose ? fullAggName.value : null,
100+
automargin: true
101+
}
102+
baseLayout.yaxis = {
103+
title: props.transpose ? null : fullAggName.value,
104+
automargin: true
105+
}
106+
}
107+
108+
return {
109+
...baseLayout,
110+
...props.layoutOptions,
111+
...props.plotlyOptions
112+
}
113+
})
114+
115+
return {
116+
chartData,
117+
layout
118+
}
119+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { computed } from 'vue'
2+
3+
export function useScatterChartData(props, rowKeys, colKeys, getAggregator) {
4+
const scatterRowKeys = computed(() => {
5+
return rowKeys.value.length > 0 ? rowKeys.value : [[]]
6+
})
7+
8+
const scatterColKeys = computed(() => {
9+
return colKeys.value.length > 0 ? colKeys.value : [[]]
10+
})
11+
12+
const chartData = computed(() => {
13+
const data = {
14+
x: [],
15+
y: [],
16+
text: [],
17+
type: 'scatter',
18+
mode: 'markers'
19+
}
20+
21+
scatterRowKeys.value.forEach((rowKey) => {
22+
scatterColKeys.value.forEach((colKey) => {
23+
const value = getAggregator(rowKey, colKey).value()
24+
if (value != null) {
25+
data.x.push(colKey.join('-'))
26+
data.y.push(rowKey.join('-'))
27+
data.text.push(value)
28+
}
29+
})
30+
})
31+
32+
return [data]
33+
})
34+
35+
const layout = computed(() => ({
36+
title: `${props.rows.join('-')} vs ${props.cols.join('-')}`,
37+
hovermode: 'closest',
38+
xaxis: {
39+
title: props.cols.join('-'),
40+
automargin: true
41+
},
42+
yaxis: {
43+
title: props.rows.join('-'),
44+
automargin: true
45+
},
46+
width: window.innerWidth / 1.5,
47+
height: window.innerHeight / 1.4 - 50
48+
}))
49+
50+
return {
51+
chartData,
52+
layout
53+
}
54+
}

0 commit comments

Comments
 (0)