Skip to content

Commit cb0f362

Browse files
authored
Merge pull request #44 from dyuri/css-props
CSS property support for stlying
2 parents 22c390d + 302801d commit cb0f362

File tree

6 files changed

+242
-13
lines changed

6 files changed

+242
-13
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
- animate() method returns an animation control object,
88
animation control methods are chainable.
9+
- CSS properties can be used to style vizzu charts
10+
E.g. `--vizzu-plot-marker-colorPalette: whatever` for `{style: {plot: {marker: {colorPalette: "whatever"}}}}`
911

1012
### Fixed
1113

@@ -42,4 +44,4 @@
4244

4345
### Added
4446

45-
- First public release
47+
- First public release

example/example.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ let anim = chart.initializing
9191
.then(chart => chart.animate({ tooltip: 8 }))*/
9292
.catch(console.log);
9393

94+
console.log(chart);
95+
9496
let slider = document.getElementById("myRange");
9597

9698
slider.oninput = (e)=>

example/index.html

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@
55
<meta charset="utf-8"/>
66
<meta name="viewport" content="width=device-width, initial-scale=1">
77
</head>
8+
<style>
9+
:root {
10+
--vizzu-plot-marker-colorPalette: #e655e8FF #123456FF #BDAF10FF;
11+
--vizzu-backgroundColor: #ffffffff;
12+
--vizzu-plot-xAxis-interlacing-color: #f7f7f7;
13+
}
14+
15+
@media (prefers-color-scheme: dark) {
16+
#vizzuCanvas {
17+
--vizzu-backgroundColor: #282828FF;
18+
--vizzu-plot-xAxis-interlacing-color: #444444FF;
19+
}
20+
}
21+
</style>
822
<body>
923
<noscript>
1024
<p class="noscript">To use Vizzu, please enable Javascript.</p>
@@ -25,4 +39,4 @@
2539

2640
<script type="module" src="example.js"></script>
2741
</body>
28-
</html>
42+
</html>

src/apps/weblib/js-api/utils.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
export const isAccessibleStylesheet = (stylesheet) => {
2+
try {
3+
stylesheet.cssRules;
4+
return true;
5+
} catch (e) {
6+
return false;
7+
}
8+
};
9+
10+
export const getCSSCustomProps = (pfx = "") =>
11+
[...document.styleSheets].filter(isAccessibleStylesheet).reduce(
12+
(finalArr, sheet) =>
13+
finalArr.concat(
14+
[...sheet.cssRules]
15+
.filter((rule) => rule.type === 1)
16+
.reduce((propValArr, rule) => {
17+
const props = [...rule.style]
18+
.filter((propName) => propName.trim().indexOf("--" + pfx) === 0)
19+
.map((propName) => propName.trim());
20+
return [...propValArr, ...props];
21+
}, [])
22+
),
23+
[]
24+
);
25+
26+
export const getCSSCustomPropsForElement = (el, pfx = "") => {
27+
const props = getCSSCustomProps(pfx);
28+
const style = getComputedStyle(el);
29+
return props
30+
.map((prop) => [prop, style.getPropertyValue(prop).trim()])
31+
.filter((pv) => pv[1] !== "");
32+
};
33+
34+
export const propSet = (obj, path, value, overwrite) => {
35+
path.reduce((acc, part, idx) => {
36+
if (idx === path.length - 1) {
37+
if (overwrite || !acc[part]) {
38+
acc[part] = value;
39+
}
40+
} else if (!acc[part]) {
41+
acc[part] = {};
42+
}
43+
44+
return acc[part];
45+
}, obj);
46+
return obj;
47+
};
48+
49+
export const propGet = (obj, path) => {
50+
return path.reduce((acc, part) => acc?.[part], obj);
51+
};
52+
53+
export const propsToObject = (props, propObj, pfx = "", overwrite = false) => {
54+
propObj = propObj || {};
55+
propObj = props.reduce((obj, [prop, val]) => {
56+
const propname = prop.replace("--" + (pfx ? pfx + "-" : ""), "");
57+
const proppath = propname.split("-");
58+
59+
propSet(obj, proppath, val, overwrite);
60+
61+
return obj;
62+
}, propObj);
63+
64+
return propObj;
65+
};

src/apps/weblib/js-api/vizzu.js

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,23 @@ import Data from "./data.js";
44
import AnimControl from "./animcontrol.js";
55
import Tooltip from "./tooltip.js";
66
import VizzuModule from "./cvizzu.js";
7+
import { getCSSCustomPropsForElement, propsToObject } from "./utils.js";
78

89
export default class Vizzu {
910
constructor(container, initState) {
1011
this.container = container;
12+
13+
if (!(this.container instanceof HTMLElement)) {
14+
this.container = document.getElementById(container);
15+
}
16+
17+
if (!this.container) {
18+
throw new Error(
19+
`Cannot find container ${this.container} to render Vizzu!`
20+
);
21+
}
22+
23+
this._propPrefix = "vizzu";
1124
this.started = false;
1225

1326
this._resolveAnimate = null;
@@ -223,7 +236,14 @@ export default class Vizzu {
223236
}
224237

225238
this.data.set(obj.data);
226-
this.setStyle(obj.style);
239+
240+
// setting style, including CSS properties
241+
const style = JSON.parse(JSON.stringify(obj.style || {}));
242+
const props = getCSSCustomPropsForElement(
243+
this.container,
244+
this._propPrefix
245+
);
246+
this.setStyle(propsToObject(props, style, this._propPrefix));
227247
this.setConfig(Object.assign({}, obj.config));
228248
}
229249
}
@@ -331,16 +351,6 @@ export default class Vizzu {
331351
let canvas = null;
332352
let placeholder = this.container;
333353

334-
if (!(placeholder instanceof HTMLElement)) {
335-
placeholder = document.getElementById(placeholder);
336-
}
337-
338-
if (!placeholder) {
339-
throw new Error(
340-
`Cannot find container ${this.container} to render Vizzu!`
341-
);
342-
}
343-
344354
if (placeholder instanceof HTMLCanvasElement) {
345355
canvas = placeholder;
346356
} else {
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import {
2+
isAccessibleStylesheet,
3+
getCSSCustomProps,
4+
getCSSCustomPropsForElement,
5+
propSet,
6+
propGet,
7+
propsToObject
8+
} from '../../../../../src/apps/weblib/js-api/utils.js';
9+
10+
11+
describe('utils.isAccessibleStylesheet()', () => {
12+
test('true -> can access `.cssRules`', () => {
13+
let stylesheet = { cssRules: [] };
14+
expect(isAccessibleStylesheet(stylesheet)).toBeTruthy();
15+
});
16+
17+
test('false -> cannot access `.cssRules`', () => {
18+
let stylesheet = { get cssRules () { throw new Error('not accessible'); } };
19+
expect(isAccessibleStylesheet(stylesheet)).toBeFalsy();
20+
});
21+
});
22+
23+
describe('utils.getCSSCustomProps()', () => {
24+
test('no stylesheet, no props', () => {
25+
global.document = {styleSheets: []}; // mockig document
26+
expect(getCSSCustomProps()).toEqual([]);
27+
});
28+
test('empty stylesheets, no props', () => {
29+
global.document = {styleSheets: [
30+
{cssRules: []},
31+
{cssRules: []}
32+
]};
33+
expect(getCSSCustomProps()).toEqual([]);
34+
});
35+
test('stylesheet with proper rules, props expected', () => {
36+
global.document = {styleSheets: [
37+
{cssRules: [
38+
{type: 1, style: ['--test-property', '--test-property-2']}
39+
]}
40+
]};
41+
expect(getCSSCustomProps()).toEqual(['--test-property', '--test-property-2']);
42+
});
43+
test('stylesheet with proper rules, using prefix, props expected', () => {
44+
global.document = {styleSheets: [
45+
{cssRules: [
46+
{type: 1, style: ['--test-property', '--no-test-property']}
47+
]}
48+
]};
49+
expect(getCSSCustomProps('test')).toEqual(['--test-property']);
50+
});
51+
});
52+
53+
describe('utils.getCSSCustomPropsForElement()', () => {
54+
test('only element related props should show up', () => {
55+
global.document = {styleSheets: [
56+
{cssRules: [
57+
{type: 1, style: ['--test-property', '--test-property-2']}
58+
]}
59+
]};
60+
global.getComputedStyle = () => {
61+
return {
62+
getPropertyValue: (prop) => {
63+
if (prop === '--test-property') {
64+
return 'test';
65+
}
66+
return '';
67+
}
68+
};
69+
};
70+
expect(getCSSCustomPropsForElement("whatever")).toEqual([['--test-property', 'test']]);
71+
});
72+
});
73+
74+
describe('utils.propSet()', () => {
75+
test('set embedded property on empty object', () => {
76+
const obj = {};
77+
propSet(obj, ['alma', 'beka', 'cica'], 'test');
78+
expect(obj?.alma?.beka?.cica).toEqual('test');
79+
});
80+
81+
test('does not overwrite by default', () => {
82+
const obj = {alma: {beka: {cica: 'notest'}}};
83+
propSet(obj, ['alma', 'beka', 'cica'], 'test');
84+
expect(obj?.alma?.beka?.cica).toEqual('notest');
85+
});
86+
87+
test('can overwrite if requested', () => {
88+
const obj = {alma: {beka: {cica: 'notest'}}};
89+
propSet(obj, ['alma', 'beka', 'cica'], 'test', true);
90+
expect(obj?.alma?.beka?.cica).toEqual('test');
91+
});
92+
});
93+
94+
describe('utils.propGet()', () => {
95+
test('get embedded property', () => {
96+
const obj = {alma: {beka: {cica: 'test'}}};
97+
expect(propGet(obj, ['alma', 'beka', 'cica'])).toEqual('test');
98+
});
99+
});
100+
101+
describe('utils.propsToObject()', () => {
102+
test('generate "deep" object from property list', () => {
103+
const props = [
104+
["--alma-beka-cica", "test"]
105+
];
106+
const obj = propsToObject(props, null);
107+
expect(propGet(obj, ['alma', 'beka', 'cica'])).toEqual('test');
108+
});
109+
110+
test('generate "deep" object from property list, using prefix', () => {
111+
const props = [
112+
["--test-alma-beka-cica", "test"]
113+
];
114+
const obj = propsToObject(props, null, "test");
115+
expect(propGet(obj, ['alma', 'beka', 'cica'])).toEqual('test');
116+
});
117+
118+
test('fill existing "deep" object from property list, using prefix, no overwrite', () => {
119+
const props = [
120+
["--test-alma-beka-cica", "test"]
121+
];
122+
const obj = {alma: {beka: {cica: 'notest'}}};
123+
propsToObject(props, obj, "test");
124+
expect(propGet(obj, ['alma', 'beka', 'cica'])).toEqual('notest');
125+
});
126+
127+
test('fill existing "deep" object from property list, using prefix, overwrite', () => {
128+
const props = [
129+
["--test-alma-beka-cica", "test"]
130+
];
131+
const obj = {alma: {beka: {cica: 'notest'}}};
132+
propsToObject(props, obj, "test", true);
133+
expect(propGet(obj, ['alma', 'beka', 'cica'])).toEqual('test');
134+
});
135+
});
136+

0 commit comments

Comments
 (0)