11<script setup>
2- import { computed, watch, Fragment, useAttrs, useSlots, useTemplateRef, onMounted, reactive } from 'vue'
2+ import { computed, watch, Fragment, useAttrs, useSlots, useTemplateRef, onMounted, reactive, provide } from 'vue'
33import { reactiveComputed, useElementSize } from '@vueuse/core'
44import { baseParse } from '@vue/compiler-core'
55import { theme_base, theme_default, themeBuild, themeMerge, themePreprocess } from '../js/theme'
@@ -22,26 +22,23 @@ function _isPropTruthy(v) {
2222 return v === "" || Boolean(v)
2323}
2424
25- const emit = defineEmits(['resize'])
25+ const emit = defineEmits(['resize', 'rangechange', 'select', 'update:range', 'update:rangePreview' ])
2626const {
2727 data: $data, scales: $scales, aes: $aes,
2828 expandAdd: $expandAdd, expandMult: $expandMult, extend: $extend,
29- levels: $levels, range: $range, minRange: $minRange,
29+ levels: $levels, range: $range, minRange: $minRange, rangePreview: $rangePreview,
3030 axes: $axes, theme: $theme, action: $action, reverse: $reverse,
3131 flip, clip, resize, legendTeleport
3232} = defineProps({
3333 data: Array, scales: Object, aes: Object,
3434 expandAdd: Object, expandMult: Object, extend: Object,
35- levels: Object, range: Object, minRange: Object,
35+ levels: Object, range: Object, minRange: Object, rangePreview: Object,
3636 axes: [Object, Array], theme: [Object, Array], action: [Object, Array], reverse: Object,
3737 flip: Boolean, clip: { type: Boolean, default: true }, resize: null,
3838 legendTeleport: null,
3939})
40- const activeSelection = defineModel('activeSelection')
41- const translateH = defineModel('translateH')
42- const translateV = defineModel('translateV')
43- const transcaleH = defineModel('transcaleH')
44- const transcaleV = defineModel('transcaleV')
40+ const selectionPreview = defineModel('selectionPreview', { default: () => ({}) })
41+ const selectionPreviewTheme = defineModel('selectionPreviewTheme', { default: () => ({}) })
4542const transition = defineModel('transition')
4643
4744function expandFragment(componentList) {
@@ -119,13 +116,6 @@ const vBind = computed(() => {
119116 wrapper[key] = $attrs[key]
120117 }
121118 }
122- for (let ori of ['x', 'y']) {
123- for (let bound of ['min', 'max']) {
124- if (primaryAxis?.[ori]?.[`onUpdate:${bound}`]) {
125- plot[`onUpdate:${ori}${bound}`] = primaryAxis?.[ori]?.[`onUpdate:${bound}`]
126- }
127- }
128- }
129119 return { plot, wrapper }
130120})
131121
@@ -143,15 +133,36 @@ const primaryAxis = reactiveComputed(() => {
143133 y: yAxes.find(c => _isPropTruthy(c.primary)) ?? yAxes.find(c => c.primary == null && !_isPropTruthy(c.secondary))
144134 }
145135})
136+
137+ const primaryAxisConfig = {
138+ levels: reactive({}),
139+ extend: reactive({}),
140+ ['min-range']: reactive({}),
141+ ['expand-add']: reactive({}),
142+ ['expand-mult']: reactive({}),
143+ boundary: reactive({}),
144+ reverse: reactive({}),
145+ min: reactive({}),
146+ max: reactive({}),
147+ ['min-preview']: reactive({}),
148+ ['max-preview']: reactive({}),
149+ }
150+
151+ watch(primaryAxis, v => {
152+ for (let ori of ['x', 'y']) {
153+ for (let key in primaryAxisConfig) {
154+ primaryAxisConfig[key][ori] = v?.[ori]?.[key]
155+ }
156+ }
157+ }, { immediate: true })
158+
146159const actionBoundary = reactiveComputed(() => {
147160 let boundary = {}
148161 for (let ori of ['x', 'y']) {
149- let bound = primaryAxis[ori]?.boundary
150- if (bound) {
151- boundary[ori] = {
152- min: bound.min ?? bound[0],
153- max: bound.max ?? bound[1]
154- }
162+ let bound = primaryAxisConfig.boundary[ori]
163+ boundary[ori] = {
164+ min: bound?.min ?? bound?.[0],
165+ max: bound?.max ?? bound?.[1]
155166 }
156167 }
157168 return boundary
@@ -165,8 +176,8 @@ const schema = computed(() => {
165176 return {
166177 data: $data,
167178 aes: $aes,
168- extendX: primaryAxis?.x?. extend ?? $extend?.x ?? 0,
169- extendY: primaryAxis?.y?. extend ?? $extend?.y ?? 0
179+ extendX: primaryAxisConfig. extend.x ?? $extend?.x ?? 0,
180+ extendY: primaryAxisConfig. extend.y ?? $extend?.y ?? 0
170181 }
171182})
172183/* layers
@@ -205,22 +216,18 @@ const levels = computed(() => (({ x, y, ...etc }) => etc)($levels ?? {}))
205216const coordLevels = computed(() => {
206217 let levels = {}
207218 for (let ori of ['x', 'y']) {
208- if (primaryAxis[ori]) {
209- let ax = primaryAxis[ori]
210- levels[ori] = ax.levels ?? $levels?.[ori]
211- }
219+ levels[ori] = primaryAxisConfig.levels[ori] ?? $levels?.[ori]
212220 }
213221 return levels
214222})
215223
216- const range = computed(() => {
224+ const coordRange = computed(() => {
217225 let result = {}
218226 for (let ori of ['x', 'y']) {
219- let ax = primaryAxis[ori]
220- let min = ax?.min ?? ax?.limits?.min ?? ax?.limits?.[0]
221- let max = ax?.max ?? ax?.limits?.max ?? ax?.limits?.[1]
222- result[ori + "min"] = isFinite(min) && !Number.isNaN(min) ? min : $range?.[ori + "min"]
223- result[ori + "max"] = isFinite(max) && !Number.isNaN(max) ? max : $range?.[ori + "max"]
227+ let min = primaryAxisConfig.min[ori] ?? $range?.[ori + "min"]
228+ let max = primaryAxisConfig.max[ori] ?? $range?.[ori + "max"]
229+ result[ori + "min"] = isFinite(min) && !Number.isNaN(min) ? min : null
230+ result[ori + "max"] = isFinite(max) && !Number.isNaN(max) ? max : null
224231 let level = coordLevels.value[ori]
225232 if (level != null) {
226233 result[ori + "min"] = (result[ori + "min"] ?? 0) - 0.5
@@ -230,31 +237,96 @@ const range = computed(() => {
230237 return result
231238})
232239
240+ const coordRangePreview = computed(() => {
241+ let result = {}
242+ for (let ori of ['x', 'y']) {
243+ let min = primaryAxisConfig['min-preview'][ori] ?? $rangePreview?.[ori + "min"]
244+ let max = primaryAxisConfig['max-preview'][ori] ?? $rangePreview?.[ori + "max"]
245+ result[ori + "min"] = isFinite(min) && !Number.isNaN(min) ? min : null
246+ result[ori + "max"] = isFinite(max) && !Number.isNaN(max) ? max : null
247+ }
248+ return result
249+ })
250+ const rangeUpdate = reactiveComputed(() => {
251+ return {
252+ xmin: primaryAxis?.x?.["onUpdate:min"],
253+ xmax: primaryAxis?.x?.["onUpdate:max"],
254+ ymin: primaryAxis?.y?.["onUpdate:min"],
255+ ymax: primaryAxis?.y?.["onUpdate:max"],
256+ }
257+ })
258+ const rangePreviewUpdate = reactiveComputed(() => {
259+ return {
260+ xmin: primaryAxis?.x?.["onUpdate:minPreview"],
261+ xmax: primaryAxis?.x?.["onUpdate:maxPreview"],
262+ ymin: primaryAxis?.y?.["onUpdate:minPreview"],
263+ ymax: primaryAxis?.y?.["onUpdate:maxPreview"],
264+ }
265+ })
266+
267+ const range = reactive({}), rangePreview = reactive({})
268+ watch(coordRange, (newRange, oldRange) => {
269+ if (newRange?.xmin !== oldRange?.xmin)
270+ range.xmin = newRange.xmin
271+ if (newRange?.xmax !== oldRange?.xmax)
272+ range.xmax = newRange.xmax
273+ if (newRange?.ymin !== oldRange?.ymin)
274+ range.ymin = newRange.ymin
275+ if (newRange?.ymax !== oldRange?.ymax)
276+ range.ymax = newRange.ymax
277+ }, { immediate: true })
278+ watch(coordRangePreview, (newRange, oldRange) => {
279+ if (newRange?.xmin !== oldRange?.xmin)
280+ rangePreview.xmin = newRange.xmin
281+ if (newRange?.xmax !== oldRange?.xmax)
282+ rangePreview.xmax = newRange.xmax
283+ if (newRange?.ymin !== oldRange?.ymin)
284+ rangePreview.ymin = newRange.ymin
285+ if (newRange?.ymax !== oldRange?.ymax)
286+ rangePreview.ymax = newRange.ymax
287+ }, { immediate: true })
288+
289+ watch(() => range, (newRange, oldRange) => {
290+ for (let key in rangeUpdate) {
291+ rangeUpdate[key]?.(newRange[key])
292+ }
293+ emit('rangechange', { ...newRange }, { ...oldRange })
294+ emit('update:range', { ...newRange })
295+ }, { deep: true })
296+ watch(() => rangePreview, (newRange) => {
297+ for (let key in rangePreviewUpdate) {
298+ rangePreviewUpdate[key]?.(newRange[key])
299+ }
300+ emit('update:rangePreview', { ...newRange })
301+ }, { deep: true })
302+ provide('range', range)
303+ provide('rangePreview', rangePreview)
304+
233305const minRange = computed(() => ({
234- x: primaryAxis?.x?.[' min-range'] ?? $minRange?.x ?? 0,
235- y: primaryAxis?.y?.[' min-range'] ?? $minRange?.y ?? 0,
306+ x: primaryAxisConfig[" min-range"].x ?? $minRange?.x ?? 0,
307+ y: primaryAxisConfig[" min-range"].y ?? $minRange?.y ?? 0,
236308}))
237309const expandAdd = computed(() => {
238- let x = primaryAxis?.x?. ['expand-add'] ?? $expandAdd?.x ?? 0,
239- y = primaryAxis?.y?. ['expand-add'] ?? $expandAdd?.y ?? 0
310+ let x = primaryAxisConfig ['expand-add'].x ?? $expandAdd?.x ?? 0,
311+ y = primaryAxisConfig ['expand-add'].y ?? $expandAdd?.y ?? 0
240312 if (Array.isArray(x)) x = { min: x[0], max: x[1] }
241313 else if (typeof x == 'number') x = { min: x, max: x }
242314 if (Array.isArray(y)) y = { min: y[0], max: y[1] }
243315 else if (typeof y == 'number') y = { min: y, max: y }
244316 return { x, y }
245317})
246318const expandMult = computed(() => {
247- let x = primaryAxis?.x?. ['expand-mult'] ?? $expandMult?.x ?? 0.05,
248- y = primaryAxis?.y?. ['expand-mult'] ?? $expandMult?.y ?? 0.05
319+ let x = primaryAxisConfig ['expand-mult'].x ?? $expandMult?.x ?? 0.05,
320+ y = primaryAxisConfig ['expand-mult'].y ?? $expandMult?.y ?? 0.05
249321 if (Array.isArray(x)) x = { min: x[0], max: x[1] }
250322 else if (typeof x == 'number') x = { min: x, max: x }
251323 if (Array.isArray(y)) y = { min: y[0], max: y[1] }
252324 else if (typeof y == 'number') y = { min: y, max: y }
253325 return { x, y }
254326})
255327const reverse = computed(() => ({
256- x: _isPropTruthy(primaryAxis?.x?.[' reverse'] ) ?? $reverse?.x ?? false,
257- y: _isPropTruthy(primaryAxis?.y?.[' reverse'] ) ?? $reverse?.y ?? false,
328+ x: _isPropTruthy(primaryAxisConfig. reverse.x ) ?? $reverse?.x ?? false,
329+ y: _isPropTruthy(primaryAxisConfig. reverse.y ) ?? $reverse?.y ?? false,
258330}))
259331
260332const buttonsMap = { left: 1, right: 2, middle: 4, X1: 8, X2: 16 }
@@ -304,12 +376,18 @@ const axes = computed(() => {
304376 return {
305377 coord, orientation, position, title, breaks, labels, minorBreaks,
306378 showGrid: _isPropTruthy(showGrid) ?? position !== "none",
307- extend: extend ?? primaryAxis?. [coord]?.extend ,
379+ extend: extend ?? primaryAxisConfig.extend [coord],
308380 theme: Object.assign({}, ...[theme.value?.axis?.[position] ?? theme.value?.axis?.[orientation]].concat($$theme)),
309381 action, ...etc,
310382 }
311383 }).filter(ax => ax != null)
312384})
385+ const paddings = reactive({})
386+ watch(axes, ax => {
387+ for (let p of ["left", "right", "top", "bottom"]) {
388+ paddings[p] = ax.some(a => a.position == p)
389+ }
390+ }, { immediate: true })
313391const action = computed(() => {
314392 return vaction.value.map(c => ({ ...c.type.$_props, ...c.props })).concat($action ?? [])
315393 .flatMap(props => {
@@ -374,6 +452,7 @@ const selections = computed(() => {
374452 buttons: buttons ?? buttonsMap[button] ?? 1,
375453 modelValue, "onUpdate:modelValue": onUpdate,
376454 theme: Object.assign({}, ...[theme.value?.selection].concat($$theme)),
455+ onSelect: (d, e) => emit('select', d, e),
377456 ...etc
378457 }
379458 })
@@ -425,13 +504,12 @@ defineExpose({
425504</script>
426505<template>
427506 <div ref="wrapper" class="vvplot" :style="wrapperStyle" v-bind="vBind.wrapper">
428- <CorePlot ref="plot" :schema="schema " :layers="layers " :range="range " :min-range="minRange"
507+ <CorePlot ref="plot" :paddings="paddings " :schema="schema " :layers="layers " :min-range="minRange"
429508 :expand-add="expandAdd" :expand-mult="expandMult" :reverse="reverse" :flip="flip"
430509 :coord-levels="coordLevels" :levels="levels" :scales="$scales" :axes="axes" :theme="theme"
431- :selections="selections" v-model:active-selection="activeSelection" v-model:transcale-h="transcaleH"
432- v-model:transcale-v="transcaleV" v-model:translate-h="translateH" v-model:translate-v="translateV"
433- v-model:transition="transition" v-bind="vBind.plot" :action="action" :clip="clip"
434- :legendTeleport="legendTeleport" />
510+ :selections="selections" v-model:transition="transition" v-bind="vBind.plot"
511+ v-model:selectionPreview="selectionPreview" v-model:selectionPreviewTheme="selectionPreviewTheme"
512+ :action="action" :clip="clip" :legendTeleport="legendTeleport" @select="(d, e) => emit('select', d, e)" />
435513 <div class="vvplot-panel-container" :style="panelStyle" v-if="vnodes.dom.panel?.length">
436514 <div class="vvplot-panel">
437515 <component v-for="c in vnodes.dom.panel" :is="c" />
0 commit comments