Custom theming and scaling #212
Replies: 8 comments 25 replies
-
Hi @tjones-ieee Thank you for your support and the food for thought, it's much appreciated:) ThemingA composable would indeed be the best way to handle dynamic config based on a theme. Here is a sample implementation: https://stackblitz.com/edit/vitejs-vite-8sszo12r?file=src%2FApp.vue
This requires a bit of work, but gets you what you need. Responsive scalingI'm not currently satisfied with the responsive state of components. I'm yet to find better ways to handle this.
|
Beta Was this translation helpful? Give feedback.
-
I appreciate your prompt reply on these two topics. I'll explore the composable approach a little further and let you know what I come up with, but I was hoping there was a way to create a custom theme to apply instead of overriding component configuration. This in particular is something I will have to do in some form regardless of the charting components used. The response state of components is really where we are struggling in being able to integrate and leverage. I feel relieved you're unsatisfied with the responsive scaling of components, and I imagine it is a very difficult task to change how it's being done. Is there a way to turn off aspects of responsiveness? Are there any users which rely on the responsiveness as-is? Here's an example of what I am attempting to resolve. It's the same chart with identical config and dataset properties. Naturally, you would expect certain things to be larger, but it tends to appear unbalanced. For example, the X-axis labels are larger in the bottom chart, as too are the markers, but there are no other changes to the look of the chart. I've experimented with turning "responsive" off and manually setting the chart height/width. Turning responsive off fixes the scaling behavior (and frankly, better respects font size) of markers and font sizes; however, in doing so it introduces additional complications because those properties are not including the title, subtitle, legend, zoom, etc. divs. I'll keep running tests and exploring things over the next week or so. |
Beta Was this translation helpful? Give feedback.
-
You can try out version 2.13.3 (I had to publish a minor instead of a beta). In your config for VueUiXy: const config = computed(() => {
return {
responsive: true,
responsiveProportionalSizing: false, // new. Default: true (previous behavior)
//... rest of your config
}
})
When set to false, I did not find a fix for the range handles centering issue. Sometimes centered, sometimes not (wtf). .vue-data-ui-zoom input[type="range"] {
top: 8px !important;
} Let me know how it goes. |
Beta Was this translation helpful? Give feedback.
-
Hi @tjones-ieee Title, Legend, Tooltips, are not part of the chart SVG element, their fontSize is always 'truthful'. Just divs all the way. Setting responsiveProportionalSizing to false just fallbacks to this default behavior, where all sizes of svg elements are proportional to the viewBox. The original intention with responsivePorportionalSizing to true, was to try and compensate this behavior by artificially scaling the sizes, depending on wether the height or width is the largest. I probably need to find a better way to handle this, but in the meantime, you can try and set the config.chart.height and width to a sweet spot matching a 'truthful' fontSize for svg text elements (dynamic font size in your config, based on a dynamic config width based on the chart container width would probably do the trick, but again, this is theory). I'm currently working on other features (out of the box date formatting config options so you can pass timestamps in the time series and have them nicely formatted with your locale), but I'll keep this in mind. X axis labels have an emit associated which you can capture, but no point in having a pointer if not used. It will be fixed in the next patch, to have the cursor as pointer only if the component consumes the @selectTimeLabel event. Cheers |
Beta Was this translation helpful? Give feedback.
-
Funny how in the dev world there's always "something else" lol
Understood, I was just providing input that they didn't appear to be affected. Wasn't entirely sure the extent of the changes you made to scaling. When you mentioned a resize observer, were you thinking you would find the Vue Data UI component div and then capture the parent from the DOM hierarchy? Or would you use an override property (new flag for responsive sizing) which uses the existing chart height property? Example with the Bullet chart I believe would do the trick: const noTitle = ref(null);
const chartTitle = ref(null);
const chartLegend = ref(null);
const chartSource = ref(null);
// vueuse, if ref is null, height/width is zero
const noTitleSize = useElementSize(noTitle);
const titleSize = useElementSize(chartTitle);
const legendSize = useElementSize(chartLegend);
const sourceSize = useElementSize(chartSource);
// bound to the SVG height
const svgHeight = computed(() => {
// chart height from config, or capture the DOM hierarchy
if (FINAL_CONFIG...chart.someNewFlag) {
// probably don't need this if
let titleHeight = 0;
if (noTitle.value) {
titleHeight = noTitleSize.height.value;
} else if (chartTitle.value) {
titleHeight = titleSize.height.value;
}
// make it so the SVG height is what shrinks to fit available space
return FINAL_CONFIG...chart.height - FINAL_CONFIG...padding - titleHeight - legendSize.height.value - sourceSize.height.value;
} else {
// handle existing functionality
return FINAL_CONFIG...chart.height;
}
}); |
Beta Was this translation helpful? Give feedback.
-
Sounds good:) I created a trunk branch you can pull and pr to. |
Beta Was this translation helpful? Give feedback.
-
Vuetify-ing the VueDataUI configIn short, there is currently no way to create a single wrapper to augment every VueDataUI config with Vuetify theme styling. Each configuration must be individually handled. Below is an example for how to do this for the vue-ui-xy component. It is not a complete example, but it provides the necessary steps to unify VueDataUI with Vuetify and can be augmented for your needs. Credit to Alec for providing some example code. Note: the mergeObjects function is the same as the import { mergeConfigs } from 'vue-data-ui'; import { computed, type Ref } from 'vue';
import { type VueUiXyConfig } from 'vue-data-ui';
import { useTheme } from 'vuetify';
//import { mergeObjects } from '@/core/utils';
function isPlainObject(v: any) {
return v !== null && typeof v === 'object' && !Array.isArray(v);
}
/**
* Merge two objects together into a new object with the unique properties from both.
* Properties from ```obj2``` will overwrite properties from ```obj1```.
*
* @param obj1 The primary object to merge.
* @param obj2 The secondary object to merge with the first.
* @returns A new merged object containing all properties from obj1 and obj2.
*/
export function mergeObjects(obj1: any = {}, obj2: any = {}) {
const out: any = {};
for (const key of new Set([...Object.keys(obj1), ...Object.keys(obj2)])) {
const dv = obj1[key],
uv = obj2[key];
if (isPlainObject(dv) && isPlainObject(uv)) {
out[key] = mergeObjects(dv, uv);
} else {
/*
if key in obj2 is defined, even if its value is undefined, we want to preserve this.
In other words, property value within obj2 is explicitly set to a value of some form,
and we should override the output (which takes precedence the property value of obj1).
*/
if (key in obj2) {
out[key] = uv;
} else {
out[key] = dv;
}
}
}
return out;
}
type iDefaultConfig = {
/** The background color based on the Vuetify theme */
bg: string;
/** The font color based on the Vuetify theme */
text: string;
/** The stroke color (for grid lines) based on the Vuetify theme */
stroke: string;
};
/**
* Wrapper composable to synchronize VueDataUI XY Chart with Vuetify's light and dark themes.
*
* @param customConfig The reactive VueDataUI component configuration object.
* @returns ```config``` - the reconstructed VueDataUI configuration object matching the Vuetify theme.
*
* @example
* const defaultConfig = ref<VueUiXyConfig>(getVueDataUiConfig('vue_ui_xy') as VueUiXyConfig);
* const { config } = useToVuetify(defaultConfig);
* // load config from the component config into defaultConfig...
*/
export function useToVuetify(customConfig: Ref<any>) {
const theme = useTheme();
const themeName = computed<'light' | 'dark'>(() => {
if (theme.current.value.dark) return 'dark';
return 'light';
});
/*
The colors to align VueDataUI with Vuetify's light/dark themes.
*/
const colors = computed<iDefaultConfig>(() => {
return {
// bg: { light: 'transparent', dark: 'transparent' }[themeName.value],
bg: theme.current.value.colors.surface,
text: { light: '#1a1a1a', dark: '#f5f5f5' }[themeName.value],
stroke: { light: '#afb0b0', dark: '#8f9090' }[themeName.value]
};
});
/*
The configuration that's synchronized with Vuetify's theme.
*/
const vuetifyConfig = computed(() => {
return {
responsive: true,
responsiveProportionalSizing: false,
chart: {
backgroundColor: colors.value.bg,
color: colors.value.text,
height: null, // must use null, not undefined
width: null,
grid: {
stroke: colors.value.stroke,
frame: {
stroke: colors.value.stroke
},
labels: {
color: colors.value.text,
xAxisLabels: {
color: colors.value.text
}
}
},
highlighter: {
color: colors.value.stroke
},
highlightArea: {
color: colors.value.stroke,
caption: {
color: colors.value.text
}
},
legend: {
color: colors.value.text
},
timeTag: {
backgroundColor: colors.value.bg,
color: colors.value.text
},
title: {
color: colors.value.text,
subtitle: {
color: colors.value.text
}
},
tooltip: {
color: colors.value.text,
backgroundColor: colors.value.bg,
backgroundOpacity: 5,
borderColor: colors.value.stroke
},
zoom: {
show: false
}
},
line: {
labels: {
color: colors.value.text
}
}
};
});
const config = computed<VueUiXyConfig>(() => {
const custom = customConfig.value || {};
const vuetify = vuetifyConfig.value || {};
return mergeObjects(custom, vuetify);
});
return { config };
} Usage exampleconst yourConfig = ref<VueUiXyConfig>(getVueDataUiConfig('vue_ui_xy') as VueUiXyConfig);
// implement whatever additional styling you require
const { config } = useToVuetify(yourConfig); <VueUiXy :dataset="dataset" :config="config" /> |
Beta Was this translation helpful? Give feedback.
-
Follow-ups to this discussion: |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Hello Alec,
I stumbled across your vue-data-ui component library and my mind was blown - not to mention it's open source. Naturally, I had to give your project a try and I was very happy with it compared to other libraries like apex.
However there are two areas where I am struggling to find a workaround for in order to adopt within the existing architecture. I have a strong feeling you'll have a good idea of how to resolve.
First is theming. To add some background here, I've adopted Vuetify as the core component library within my Vue apps. Hate it or love it, I personally find it extremely well put together and extensible. The issue that arises is how Vuetify handles app level theming versus how vue-data-ui component theming works. I see in the docs there are built in themes, but I wasn't able to figure out how to create dynamic theming in order to match the light/dark mode (or whatever custom theming someone might use in their Vuetify app). I did not try everything under the sun, but things that I thought might work didn't appear to work as expected, but for example I did not try using composables to dynamically reproduce the config of a chart based on the Vuetify theme change. I feel like there's a way to create a config wrapper to do this.
Second is scaling. Basically, the use case is a pure CSS flexing of width and height where a dashboard flexes to fill available space. In my use cases, dashboards are viewed on variable screen sizes and resolutions - which requires components within the chart to scale respectively. However, the scaling of charts with the responsive property tend to scale vue-data-ui components as a vector image rather than, for example, increasing the overall size of the chart while leaving the line, markers, font, labels, etc. unchanged. You can see this if you were to add a component within something like split panes, and the responsive scaling is quite dramatic which ends up shrinking the chart area due to the growth of the legend.
I really appreciate the work you're doing here. I don't understand how it is not more widely known, to be honest!
Thanks,
Tyler
Edit 2025-07-10: Working solution at the bottom of this discussion.
Beta Was this translation helpful? Give feedback.
All reactions