@cnrs/hecate-views-charts 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,134 @@
1
+ {
2
+ "@comment comments": [
3
+ "To track and understand the whys of each part of this package.json, ",
4
+ "in particular dependencies, please explain when you modify this file.",
5
+ "Please include the name of the section you comment after the `@comment`",
6
+ "'annotation' to make sure each key is unique (see the JSON spec).",
7
+ "This is required by JSON validators (like the one built into your IDE).",
8
+ "Plus, running `npm install <whatever> --save` removes duplicate keys.",
9
+ "",
10
+ "package.json documentation: https://docs.npmjs.com/files/package.json"
11
+ ],
12
+ "name": "@cnrs/hecate-views-charts",
13
+ "description": "Views web components available to any web app.",
14
+ "keywords": [
15
+ "dataviz",
16
+ "research",
17
+ "database",
18
+ "visualization",
19
+ "scientific",
20
+ "javascript",
21
+ "browser",
22
+ "library"
23
+ ],
24
+ "type": "module",
25
+ "module": "src/index.js",
26
+ "main": "src/index.js",
27
+ "exports": {
28
+ ".": "./src/index.js",
29
+ "default": "./src/index.js"
30
+ },
31
+ "files": [
32
+ "src/**/*.js"
33
+ ],
34
+ "version": "0.2.0",
35
+ "license": "AGPL",
36
+ "homepage": "https://datasphere.readthedocs.io/projects/hecate/",
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://gitlab.huma-num.fr/datasphere/hecate/views/charts.git"
40
+ },
41
+ "bugs": {
42
+ "url": "https://gitlab.huma-num.fr/datasphere/hecate/views/charts/-/issues"
43
+ },
44
+ "main": "src/index.js",
45
+ "dependencies": {
46
+ "d3": "latest"
47
+ },
48
+ "@comment devDependencies": {
49
+ "vite": [
50
+ "Bundler for javascript project combines many js source files into a ",
51
+ "single one that is production-ready loadable in the browser.",
52
+ "- ViteJS website: https://vitejs.dev/"
53
+ ],
54
+ "vitest": [
55
+ "More or less 'all in one' unit testing framework",
56
+ "@requires vite ≥ 3.0.0 (and node ≥ 14.18)",
57
+ "- Vitest website: https://vitest.dev/"
58
+ ],
59
+ "@vitest/coverage-c8": [
60
+ "Compute percentage of code covered by tests.",
61
+ "- Vitest coverage documentation: https://vitest.dev/guide/coverage.html"
62
+ ],
63
+ "happy-dom": [
64
+ "By default, tests framework don't have any access to the DOM.",
65
+ "This packages simulates a DOM in the context of tests.",
66
+ "- https://www.npmjs.com/package/happy-dom"
67
+ ],
68
+ "eslint": [
69
+ "To use eslint ECMAScript linter.",
70
+ "- ESLint documentation: https://eslint.org/"
71
+ ],
72
+ "jsdoc": [
73
+ "Javascript documentation generation tool.",
74
+ "See `gendoc` script in this same file for some usage details.",
75
+ "- JSDoc documentation: https://jsdoc.app/"
76
+ ]
77
+ },
78
+ "devDependencies": {
79
+ "vite": "latest",
80
+ "vitest": "latest",
81
+ "@vitest/coverage-v8": "latest",
82
+ "happy-dom": "<=12.1.5",
83
+ "eslint": "latest",
84
+ "jsdoc": "latest"
85
+ },
86
+ "@comment scripts": {
87
+ "dev": [
88
+ "Starts development server in the local folder: this allows us devs to ",
89
+ "rapidly see the impacts of our modifications, with no page refresh.",
90
+ "@requires vite (dev dependency)"
91
+ ],
92
+ "test": [
93
+ "Runs every unit test once, then exits.",
94
+ "@requires vitest (dev dependency)"
95
+ ],
96
+ "test:coverage": [
97
+ "Runs every unit test once, with coverage percentage.",
98
+ "@requires vitest @vitest/coverage-v8 (dev dependencies)"
99
+ ],
100
+ "test:watch": [
101
+ "Runs every unit test with coverage %, watching for modifications.",
102
+ "@requires vitest @vitest/coverage-v8 (dev dependencies)"
103
+ ],
104
+ "build": [
105
+ "Bundles project for production in `dist` folder.",
106
+ "@requires vite (dev dependency)"
107
+ ],
108
+ "lint": [
109
+ "Checks code style using ESLint. note: Argument pattern is surrounded with ",
110
+ "double quotes, so its resolution doesn't depend on runtime environment.",
111
+ "@requires eslint (dev dependency)",
112
+ "- ESLint configuration file is `eslint.config.mjs`",
113
+ "- ESLint documentation: https://eslint.org/"
114
+ ],
115
+ "doc:html": [
116
+ "Generates developper documentation using JSDoc.",
117
+ "Documentation for each module/class/method is written",
118
+ "in docstring format, directly inside each source file.",
119
+ "@requires jsdoc (dev dependency)",
120
+ "- JSDoc configuration file is `.jsdoc.conf.json`.",
121
+ "- JSDoc config file spec: https://jsdoc.app/about-configuring-jsdoc.html",
122
+ "- JSDoc repo: https://github.com/jsdoc/jsdoc"
123
+ ]
124
+ },
125
+ "scripts": {
126
+ "dev": "vite",
127
+ "test": "vitest run",
128
+ "test:coverage": "vitest run --coverage",
129
+ "test:watch": "vitest --coverage",
130
+ "build": "vite build",
131
+ "lint": "eslint \"src/**/*.js\"",
132
+ "doc:html": "./node_modules/jsdoc/jsdoc.js -c .jsdoc.conf.json"
133
+ }
134
+ }
@@ -0,0 +1 @@
1
+ export { Sample } from './sample.js';
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Sample web component.
3
+ * It just displays a string.
4
+ */
5
+ class Sample extends HTMLElement {
6
+
7
+ constructor() {
8
+ super();
9
+ this.attachShadow({ mode: 'open' });
10
+ this.data = "placeholder";
11
+ }
12
+ /** Static property returning an array of attributes observed by the browser
13
+ */
14
+ static get observedAttributes() {
15
+ return ['data'];
16
+ }
17
+ /**
18
+ * This is called when an observed attribute is
19
+ * defined in the HTML or changed using Javascript.
20
+ */
21
+ attributeChangedCallback(property, old, now) {
22
+ if (old === now) return; // don't bother to render if there is nothing new
23
+ this[property] = now;
24
+ }
25
+
26
+ /**
27
+ * Called when the element is connected to a DOM.
28
+ * Runs any required rendering.
29
+ */
30
+ connectedCallback() {
31
+ this.shadowRoot.innerHTML += `<p>${this.data}</p>`;
32
+ }
33
+ }
34
+
35
+
36
+
37
+ export { Sample };
package/src/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from './views/index.js';
2
+ export * from './components/index.js';
package/src/init.js ADDED
@@ -0,0 +1,8 @@
1
+ import { Sample, } from './components/';
2
+ import { BarChart, Histogram, ScatterPlot, LineChart } from './views/';
3
+
4
+ customElements.define('sample-component', Sample);
5
+ customElements.define('scatter-plot', ScatterPlot);
6
+ customElements.define('bar-chart', BarChart);
7
+ customElements.define('histogram-chart', Histogram);
8
+ customElements.define('line-chart', LineChart);
@@ -0,0 +1,246 @@
1
+ import * as d3 from 'd3';
2
+
3
+
4
+ /**
5
+ * BarChart web component.
6
+ * @see https://en.wikipedia.org/wiki/Bar_chart
7
+ */
8
+ class BarChart extends HTMLElement{
9
+
10
+ constructor(){
11
+ super();
12
+ this.attachShadow({ mode: 'open' });
13
+ this.hera = {};
14
+ this.settings = {};
15
+ }
16
+
17
+ /** Static property returning an array of attributes observed by the browser
18
+ */
19
+ static get observedAttributes() {
20
+ return ['data-hera', 'data-settings'];
21
+ }
22
+
23
+ get items() { return this.hera.items || []; }
24
+ get entities() { return this.hera.entities || []; }
25
+ get properties() { return this.hera.properties || []; }
26
+
27
+ get pidx() { return this.settings.x || 'x'; }
28
+ get pidy() { return this.settings.y || 'y'; }
29
+ get color() { return this.settings.color || '#7b4c98'; }
30
+
31
+ /**
32
+ * This is called when an observed attribute is
33
+ * defined in the HTML or changed using Javascript.
34
+ * @param {!string} attribute - Attribute name
35
+ * @param old - Previous value for `attribute`
36
+ * @param now - Current value for `attribute`
37
+ */
38
+ attributeChangedCallback(attribute, old, now) {
39
+ if (old === now) return; // don't bother if there is nothing new
40
+ this[attribute] = now;
41
+ // data-* attributes can only be strings, so we cannot deserialize JSON
42
+ // in them ; so we use plain object fields to store deserialized values
43
+ // we trim the prefix to get the field name, so 'data-x' becomes 'x'
44
+ const fieldName = attribute.split('-').slice(1).join('-');
45
+ this[fieldName] = JSON.parse(now);
46
+ // note that this does not defeat the purpose of using data-* attributes
47
+ // in the first place, as if a name clash should happen in the future,
48
+ // we could edit this class with no impact on the users code
49
+ }
50
+
51
+ //js method called everytime the component is added to the dom
52
+ connectedCallback(){
53
+ var dataset = this.prepareData(this.hera, this.settings);
54
+
55
+ //creation of scene
56
+ const margin = { top : 70, right : 30, bottom: 40, left : 80};
57
+ const {sceneWidth, sceneHeight, barWidth, svg} = this.createScene(500, 500, this.items.length, margin);
58
+
59
+ //addition of axises
60
+ var {y_scale, x_scale} = this.createAxisScales(sceneHeight, sceneWidth, dataset);
61
+ this.addAxises(x_scale, y_scale, sceneHeight, sceneWidth, margin, svg)
62
+
63
+ this.displayChart(sceneHeight, barWidth, dataset, y_scale, x_scale, svg);
64
+
65
+ }
66
+
67
+ /**
68
+ * Sets the shadow dom of the component, currently creates a single div
69
+ * @returns the element that was appended to the shadow dom
70
+ */
71
+ createShadowDom() {
72
+ const e = document.createElement('div');
73
+ this.shadowRoot.appendChild(e);
74
+ return e;
75
+ }
76
+
77
+ /**
78
+ * Prepare the data by giving it the correct format
79
+ * @param {object} rawData - the data as it was passed , it's a js object type
80
+ * @param {object} rawSettings - the settings as it was passed, it's a js object type
81
+ * @returns js object that contains the elements of metadata, each key representing a different pid
82
+ */
83
+ prepareData(rawData, rawSettings){
84
+ var barValues = [];
85
+ var barLabels = [];
86
+ rawData.items.forEach(item => {
87
+ item.metadata.forEach(meta => {
88
+ if (meta.pid === this.pidy) { // Y axis is height of bars
89
+ barValues.push(parseInt(meta.value));
90
+ }
91
+ if (meta.pid === this.pidx) { // X axis is "label for each bar"
92
+ barLabels.push(meta.value);
93
+ }
94
+ });
95
+ });
96
+ const dataset = barLabels.map((label, index) => ({
97
+ label: label,
98
+ value: barValues[index]
99
+ }));
100
+ return dataset;
101
+ }
102
+
103
+ /**
104
+ * creates a svg element that will contain the viewer
105
+ * @param {number} width - desired width for the svg
106
+ * @param {number} height - desired height for the svg
107
+ * @param {number} dataLength - number of elements in data
108
+ * @param {object} margin - to leave space for the axises, a js object containing 4 margins
109
+ * @returns the svg and its height, width and the width of each bars
110
+ */
111
+ createScene(width, height, dataLength, margin){
112
+
113
+ const sceneWidth = width - margin.left - margin.right; //to set barwidth, the key scenewith in the return is not recognized yet
114
+ const sceneHeight = height - margin.top - margin.bottom;
115
+ const barWidth = sceneWidth/dataLength
116
+
117
+ const divElem = this.createShadowDom();
118
+ const container = d3.select(divElem);
119
+
120
+ //addition of container to the svg
121
+ const svg = container.append("svg")
122
+ .attr("width", sceneWidth + margin.left + margin.right)
123
+ .attr("height", sceneHeight + margin.top + margin.bottom)
124
+ .append("g")
125
+ .attr("transform", `translate(${margin.left},${margin.top})`);
126
+
127
+ return {
128
+ sceneWidth : sceneWidth,
129
+ sceneHeight : sceneHeight,
130
+ barWidth : barWidth,
131
+ svg : svg
132
+ };
133
+ }
134
+
135
+ /**
136
+ * create the x and y linear scales
137
+ * @param {number} sceneHeight
138
+ * @param {number} sceneWidth
139
+ * @param {object} dataset
140
+ * @returns y_scale and x_scales
141
+ */
142
+ createAxisScales(sceneHeight, sceneWidth, dataset){
143
+ var y_scale = d3.scaleLinear()
144
+ .range([sceneHeight,0])
145
+ .domain([0,d3.max(dataset.map(function(d) { return d.value; }))]);
146
+
147
+ var x_scale = d3.scaleBand()
148
+ .range([ 0, sceneWidth ])
149
+ .domain(dataset.map(function(d) { return d.label; }))
150
+ .padding(0.2);
151
+
152
+ return{
153
+ y_scale : y_scale,
154
+ x_scale : x_scale
155
+ }
156
+ }
157
+
158
+ /**
159
+ * add the left and bottom axises, with their Label
160
+ * @param {map} x_scale - linear scale for the x axis
161
+ * @param {map} y_scale - linear scale for the y axis
162
+ * @param {number} sceneHeight - the height of the svg
163
+ * @param {number} sceneWidth - the width of the svg
164
+ * @param {object} margins
165
+ * @param {object} dataset
166
+ * @param {tag} svg
167
+ */
168
+ addAxises(x_scale, y_scale, sceneHeight, sceneWidth, margins, svg){
169
+ var y_axis = d3.axisLeft(y_scale);
170
+ var x_axis = d3.axisBottom(x_scale);
171
+ const labels = this.getAxesLabels();
172
+
173
+ svg.append("g")
174
+ .call(y_axis)
175
+ /*TO DO : allow the user to customize the appearance of the axis
176
+ the line below removes the line of vertical axis*/
177
+ //.call((g) => g.select(".domain").remove())
178
+ .call((g) => g.append("text")
179
+ .attr("x", -margins.left)
180
+ .attr("y",-10)
181
+ .attr("fill", "currentColor")
182
+ .attr("text-anchor", "start")
183
+ .text(labels.y));
184
+
185
+ svg.append("g")
186
+ .attr("transform", `translate(0,${sceneHeight})`)
187
+ .call(x_axis)
188
+ .call((g) => g.append("text")
189
+ .attr("x", sceneWidth)
190
+ .attr("y", margins.bottom - 4)
191
+ .attr("fill", "currentColor")
192
+ .attr("text-anchor", "end")
193
+ .text(labels.x))
194
+ .selectAll("text");
195
+ //TO DO : this lines allows to rotate the text of the horizontal axis
196
+ //.attr("transform", `translate(-10,0)rotate(-45)`)
197
+
198
+ }
199
+
200
+ /**
201
+ * display the bar and puts it in the svg
202
+ * @param {number} sceneHeight - the height of the svg
203
+ * @param {number} barWidth - the width of each bars
204
+ * @param {object} dataset
205
+ * @param {map} y_scale
206
+ * @param {map} x_scale
207
+ * @param {*} svg
208
+ */
209
+ displayChart(sceneHeight, barWidth, dataset, y_scale, x_scale, svg){
210
+ svg.selectAll("rect")
211
+ .data(dataset)
212
+ .enter()
213
+ .append("rect")
214
+ .attr("y", function(d) {
215
+ return y_scale(d.value);
216
+ })
217
+ .attr("x", function(d) {
218
+ return x_scale(d.label);
219
+ })
220
+ .attr("height", function(d) {
221
+ return sceneHeight - y_scale(d.value);
222
+ })
223
+ .attr("width", barWidth) //add bar distance in future?
224
+
225
+ .attr("fill", this.color)
226
+ .attr("stroke","black");
227
+ }
228
+
229
+ getAxesLabels() {
230
+ const px = this.getProperty(this.pidx);
231
+ const py = this.getProperty(this.pidy);
232
+ return {
233
+ x: px ? px.name : `Missing property '${this.pidx}'`,
234
+ y: py ? py.name : `Missing property '${this.pidy}'`,
235
+ };
236
+ }
237
+
238
+ getProperty(pid) {
239
+ const found = this.properties.find((p) => p['@id'] === pid);
240
+ return found ? found : null;
241
+ }
242
+ }
243
+
244
+
245
+
246
+ export { BarChart };
@@ -0,0 +1,222 @@
1
+ import * as d3 from 'd3';
2
+
3
+
4
+ /**
5
+ * Histogram web component.
6
+ * @see https://en.wikipedia.org/wiki/Histogram
7
+ */
8
+ class Histogram extends HTMLElement{
9
+
10
+ constructor() {
11
+ super();
12
+ this.attachShadow({ mode: 'open' });
13
+ this.hera = {};
14
+ this.settings = {};
15
+ }
16
+ /**
17
+ * Static property returning the array of attributes
18
+ * observed by the browser.
19
+ * @returns {Array<string>} Attribute names
20
+ */
21
+ static get observedAttributes() {
22
+ return ['data-hera', 'data-settings'];
23
+ }
24
+
25
+ get items() { return this.hera.items || []; }
26
+ get entities() { return this.hera.entities || []; }
27
+ get properties() { return this.hera.properties || []; }
28
+
29
+ get pidx() { return this.settings.x || 'x'; }
30
+ get pidy() { return this.settings.y || 'y'; }
31
+ get color() { return this.settings.color || '#7b4c98'; }
32
+
33
+ get padding() {
34
+ return this.settings.padding
35
+ || { top: 20, right: 20, bottom: 30, left: 40 };
36
+ }
37
+
38
+ /**
39
+ * This is called when an observed attribute is
40
+ * defined in the HTML or changed using Javascript.
41
+ * @param {!string} attribute - Attribute name
42
+ * @param old - Previous value for `attribute`
43
+ * @param now - Current value for `attribute`
44
+ */
45
+ attributeChangedCallback(attribute, old, now) {
46
+ if (old === now) return; // don't bother if there is nothing new
47
+ this[attribute] = now;
48
+ // data-* attributes can only be strings, so we cannot deserialize JSON
49
+ // in them ; so we use plain object fields to store deserialized values
50
+ // we trim the prefix to get the field name, so 'data-x' becomes 'x'
51
+ const fieldName = attribute.split('-').slice(1).join('-');
52
+ this[fieldName] = JSON.parse(now);
53
+ // note that this does not defeat the purpose of using data-* attributes
54
+ // in the first place, as if a name clash should happen in the future,
55
+ // we could edit this class with no impact on the users code
56
+ }
57
+
58
+ /**
59
+ * Called when the element is connected to a DOM.
60
+ * Runs any required rendering.
61
+ */
62
+ connectedCallback(){
63
+
64
+ this.width = this.parentElement.clientWidth;
65
+ this.height = this.parentElement.clientHeight;
66
+
67
+ const container = document.createElement('div');
68
+ this.shadowRoot.appendChild(container);
69
+
70
+ // group items into "bins", so that scene creation is easier
71
+ const bins = this.prepareData();
72
+ // create viewport, ie. d3 "scale" functions for axes
73
+ const { vx, vy } = this.createViewport(bins);
74
+ // create a SVG container for the whole scene
75
+ const scene = d3.select(container).append('svg')
76
+ .attr('width', this.width)
77
+ .attr('height', this.height)
78
+ .attr('viewBox', [0, 0, this.width, this.height])
79
+ .attr('style', 'max-width: 100%; height: auto;')
80
+ ;
81
+ // create callback for visual feedback when hovering on each histogram bar
82
+ const { mouseover, mouseleave } = this.createHoverCallbacks();
83
+ // create histogram bars
84
+ scene.append('g')
85
+ .attr('fill', this.color)
86
+ .selectAll()
87
+ .data(bins)
88
+ .join('rect')
89
+ .attr('x', (d) => vx(d.x0) + 1)
90
+ .attr('width', (d) => vx(d.x1) - vx(d.x0) - 1)
91
+ .attr('y', (d) => vy(d.length))
92
+ .attr('height', (d) => vy(0) - vy(d.length))
93
+ .on('mouseover', mouseover)
94
+ .on('mouseleave', mouseleave)
95
+ .append('title')
96
+ .text((bin) => this.getBinLabel(bin))
97
+ ;
98
+ // add X axis and Y axis to scene
99
+ this.addAxes(scene, vx, vy);
100
+ }
101
+
102
+ /**
103
+ * Prepare the data by organize so that scene creation is simpler.
104
+ * For this, we need to turn quantitative values into consecutive,
105
+ * non-overlapping intervals called "bins", which we can in turn use
106
+ * to group values in nice histogram bars.
107
+ * @return Bin generator
108
+ * @see https://d3js.org/d3-array/bin
109
+ */
110
+ prepareData() {
111
+ var barValues = [];
112
+ var classValues = [];
113
+ this.items.forEach(item => {
114
+ item.metadata.forEach(meta => {
115
+ if (meta.pid === this.pidx) {
116
+ barValues.push(parseInt(meta.value));
117
+ }
118
+ if (meta.pid === this.pidy){
119
+ classValues.push(meta.value);
120
+ }
121
+ });
122
+ });
123
+ const dataset = classValues.map((label, index) => ({
124
+ class: label,
125
+ value: barValues[index],
126
+ }));
127
+ // Now, "bin" the dataset:
128
+ const bins = d3.bin()
129
+ //.thresholds(40)
130
+ // if thresholds are specified as a number, the domain will be uniformly
131
+ // divided into approximately that many bins: so if .thresholds(40) is
132
+ // called, the resulting Histogram will have _appoximately_ 40 bars;
133
+ // if thresholds is not specified, the current threshold generator will
134
+ // be used, and by default it implements Sturges’ formula
135
+ .value((d) => d.value)(dataset);
136
+ return bins;
137
+ }
138
+
139
+ /**
140
+ * Creates callbacks for changing each bar color when hovering onto.
141
+ */
142
+ createHoverCallbacks(msg) {
143
+ return {
144
+ mouseover: function(d) {
145
+ d3.select(this).style('opacity', .5)
146
+ },
147
+ mouseleave: function(d) {
148
+ d3.select(this).style('opacity', 1)
149
+ },
150
+ };
151
+ }
152
+
153
+ /**
154
+ * Creates two scale functions. vx (resp. vy) is used to convert x (resp. y)
155
+ * to a number between 0 and the width (resp. height) of the viewer.
156
+ * @param {Array<Object>} bins - Array containing bins
157
+ * @returns {Viewport} This viewer current viewport
158
+ */
159
+ createViewport(bins){
160
+ return {
161
+ vx: d3.scaleLinear()
162
+ .domain([bins[0].x0, bins[bins.length - 1].x1])
163
+ .range([this.padding.left, this.width - this.padding.right]),
164
+ vy: d3.scaleLinear()
165
+ .domain([0, d3.max(bins, (d) => d.length)])
166
+ .range([this.height - this.padding.bottom, this.padding.top]),
167
+ };
168
+ }
169
+
170
+ getBinLabel(bin) {
171
+ const py = this.getProperty(this.pidy);
172
+ const name = py ? py.name : `Missing property '${this.pidy}'`;
173
+ return `No. of ${name}: ${bin.length}`;
174
+ }
175
+
176
+ /**
177
+ * Add X and Y axes to the SVG scene.
178
+ */
179
+ addAxes(svg, vx, vy) {
180
+ const labels = this.getAxesLabels();
181
+ // add X-axis and label
182
+ svg.append('g')
183
+ .attr('transform', `translate(0,${this.height - this.padding.bottom})`)
184
+ .call(d3.axisBottom(vx).ticks(this.width / 80).tickSizeOuter(0))
185
+ .call((g) => g.append('text')
186
+ .attr('x', this.width)
187
+ .attr('y', this.padding.bottom - 4)
188
+ .attr('fill', 'currentColor')
189
+ .attr('text-anchor', 'end')
190
+ .text(labels.x)
191
+ );
192
+ // add Y-axis and label
193
+ svg.append('g')
194
+ .attr('transform', `translate(${this.padding.left},0)`)
195
+ .call(d3.axisLeft(vy).ticks(this.height / 40))
196
+ .call((g) => g.append('text')
197
+ .attr('x', -this.padding.left)
198
+ .attr('y', 10)
199
+ .attr('fill', 'currentColor')
200
+ .attr('text-anchor', 'start')
201
+ .text(`Frequency (#${labels.y})`)
202
+ );
203
+ }
204
+
205
+ getAxesLabels() {
206
+ const px = this.getProperty(this.pidx);
207
+ const py = this.getProperty(this.pidy);
208
+ return {
209
+ x: px ? px.name : `Missing property '${this.pidx}'`,
210
+ y: py ? py.name : `Missing property '${this.pidy}'`,
211
+ };
212
+ }
213
+
214
+ getProperty(pid) {
215
+ const found = this.properties.find((p) => p['@id'] === pid);
216
+ return found ? found : null;
217
+ }
218
+ }
219
+
220
+
221
+
222
+ export { Histogram };
@@ -0,0 +1,12 @@
1
+ export { BarChart } from './barchart.js';
2
+ export { Histogram } from './histogram.js';
3
+ export { LineChart } from './linechart.js';
4
+ export { ScatterPlot } from './scatterplot.js';
5
+
6
+ /**
7
+ * This object is a wrapper for three arrays
8
+ * @typedef {Object} Hera
9
+ * @property {Array<Object>} items - Items list
10
+ * @property {Array<Object>} entities - Entities list
11
+ * @property {Array<Object>} properties - Properties list
12
+ */