@cnrs/hecate-views-charts 0.2.0 → 0.5.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/README.md +21 -4
- package/package.json +45 -20
- package/src/init.js +8 -8
- package/src/util.js +13 -0
- package/src/views/all.js +6 -0
- package/src/views/barchart.js +37 -63
- package/src/views/cartesianchart.js +309 -0
- package/src/views/histogram.js +19 -51
- package/src/views/index.js +6 -4
- package/src/views/linechart.js +76 -213
- package/src/views/scatterchart.js +87 -0
- package/src/views/stackedareachart.js +126 -0
- package/src/views/streamgraph.js +39 -0
- package/src/components/index.js +0 -1
- package/src/components/sample.js +0 -37
- package/src/views/scatterplot.js +0 -193
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { StackedAreaChart } from './stackedareachart.js';
|
|
2
|
+
import * as d3 from 'd3';
|
|
3
|
+
|
|
4
|
+
class Streamgraph extends StackedAreaChart {
|
|
5
|
+
|
|
6
|
+
createYDomain(parsers) {
|
|
7
|
+
return d3.extent(this._stacked_series.flat(2))
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
createStackOffset() { return d3.stackOffsetWiggle; }
|
|
11
|
+
createStackOrder() { return d3.stackOrderInsideOut; }
|
|
12
|
+
|
|
13
|
+
renderVAxis(svg, plot, axis) {
|
|
14
|
+
const xOffset = plot.padding.left;
|
|
15
|
+
const yOffset = plot.padding.top;
|
|
16
|
+
// append the y-axis, while extending 0% & 100% ticks to become top
|
|
17
|
+
// and bottom ticker "main" lines. Add its label, too.
|
|
18
|
+
axis.attr('transform', `translate(${xOffset},${yOffset})`)
|
|
19
|
+
.call(
|
|
20
|
+
d3.axisLeft(plot.vy)
|
|
21
|
+
.ticks(plot.height / 80)
|
|
22
|
+
.tickFormat((d) => Math.abs(d).toLocaleString('en-US'))
|
|
23
|
+
)
|
|
24
|
+
.call(g => g.select('.domain').remove())
|
|
25
|
+
.call(
|
|
26
|
+
g => g.selectAll('.tick line')
|
|
27
|
+
.clone()
|
|
28
|
+
.attr('x2', plot.width)
|
|
29
|
+
.attr('stroke-opacity', 0.1)
|
|
30
|
+
);
|
|
31
|
+
svg.append(() => axis.node());
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
customElements.define('hecate-streamgraph', Streamgraph);
|
|
38
|
+
|
|
39
|
+
export { Streamgraph };
|
package/src/components/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { Sample } from './sample.js';
|
package/src/components/sample.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
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/views/scatterplot.js
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import * as d3 from 'd3';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Scatterplot web component.
|
|
6
|
-
* @see https://en.wikipedia.org/wiki/Scatter_plot
|
|
7
|
-
* @see https://scottmurray.org/tutorials/d3/
|
|
8
|
-
*/
|
|
9
|
-
class ScatterPlot extends HTMLElement {
|
|
10
|
-
|
|
11
|
-
constructor() {
|
|
12
|
-
super();
|
|
13
|
-
this.attachShadow({ mode: 'open' });
|
|
14
|
-
this.hera = {};
|
|
15
|
-
this.settings = {};
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Static property returning the array of attributes
|
|
19
|
-
* observed by the browser.
|
|
20
|
-
* @returns {Array<string>} Attribute names
|
|
21
|
-
*/
|
|
22
|
-
static get observedAttributes() {
|
|
23
|
-
return ['data-hera', 'data-settings'];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
get items() { return this.hera.items || []; }
|
|
27
|
-
get entities() { return this.hera.entities || []; }
|
|
28
|
-
get properties() { return this.hera.properties || []; }
|
|
29
|
-
|
|
30
|
-
get pidx() { return this.settings.x || 'x'; }
|
|
31
|
-
get pidy() { return this.settings.y || 'y'; }
|
|
32
|
-
get label() { return this.settings.label || 'label'; }
|
|
33
|
-
get color() { return this.settings.color || '#7b4c98'; }
|
|
34
|
-
|
|
35
|
-
get padding() {
|
|
36
|
-
return this.settings.padding
|
|
37
|
-
|| { top: 3, right: 0, bottom: 10, left: 30 };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* This is called when an observed attribute is
|
|
42
|
-
* defined in the HTML or changed using Javascript.
|
|
43
|
-
* @param {!string} attribute - Attribute name
|
|
44
|
-
* @param old - Previous value for `attribute`
|
|
45
|
-
* @param now - Current value for `attribute`
|
|
46
|
-
*/
|
|
47
|
-
attributeChangedCallback(attribute, old, now) {
|
|
48
|
-
if (old === now) return; // don't bother if there is nothing new
|
|
49
|
-
this[attribute] = now;
|
|
50
|
-
// data-* attributes can only be strings, so we cannot deserialize JSON
|
|
51
|
-
// in them ; so we use plain object fields to store deserialized values
|
|
52
|
-
// we trim the prefix to get the field name, so 'data-x' becomes 'x'
|
|
53
|
-
const fieldName = attribute.split('-').slice(1).join('-');
|
|
54
|
-
this[fieldName] = JSON.parse(now);
|
|
55
|
-
// note that this does not defeat the purpose of using data-* attributes
|
|
56
|
-
// in the first place, as if a name clash should happen in the future,
|
|
57
|
-
// we could edit this class with no impact on the users code
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Called when the element is connected to a DOM.
|
|
62
|
-
* Runs any required rendering.
|
|
63
|
-
*/
|
|
64
|
-
connectedCallback() {
|
|
65
|
-
this.width = this.parentElement.clientWidth;
|
|
66
|
-
this.height = this.parentElement.clientHeight;
|
|
67
|
-
const svgWidth = this.width + this.padding.right + this.padding.left;
|
|
68
|
-
const svgHeight = this.height + this.padding.top + this.padding.bottom;
|
|
69
|
-
|
|
70
|
-
const container = document.createElement('div');
|
|
71
|
-
this.shadowRoot.appendChild(container);
|
|
72
|
-
const svg = d3.select(container)
|
|
73
|
-
.append('svg')
|
|
74
|
-
.attr('viewBox', `0 0 ${svgWidth} ${svgHeight}`)
|
|
75
|
-
;
|
|
76
|
-
const {vx, vy} = this.viewport;
|
|
77
|
-
|
|
78
|
-
// create a dot for each item in this.items
|
|
79
|
-
svg.selectAll('circle')
|
|
80
|
-
.data(this.items)
|
|
81
|
-
.enter()
|
|
82
|
-
.append('circle')
|
|
83
|
-
.attr('cx', (item) => vx(this.getItemX(item)))
|
|
84
|
-
.attr('cy', (item) => vy(this.getItemY(item)))
|
|
85
|
-
.attr('r', (item) => this.getItemRadius(item))
|
|
86
|
-
.attr('fill', (item) => this.getItemColor(item))
|
|
87
|
-
.append('title')
|
|
88
|
-
.text((item) => this.getItemLabel(item))
|
|
89
|
-
;
|
|
90
|
-
svg.selectAll('circle')
|
|
91
|
-
.on('click', (event) => console.log(event));
|
|
92
|
-
|
|
93
|
-
var xAxis = d3.axisBottom(vx).ticks(11);
|
|
94
|
-
var yAxis = d3.axisLeft(vy).ticks(2);
|
|
95
|
-
const labels = this.getAxesLabels();
|
|
96
|
-
svg.append('g')
|
|
97
|
-
.attr('transform', `translate(0,${this.height-this.padding.top})`)
|
|
98
|
-
.call(xAxis)
|
|
99
|
-
.call((g) => g.append('text')
|
|
100
|
-
.attr('x', this.width)
|
|
101
|
-
.attr('y', 0)
|
|
102
|
-
.attr('fill', 'black')
|
|
103
|
-
.attr('text-anchor', 'end')
|
|
104
|
-
.text(labels.y))
|
|
105
|
-
;
|
|
106
|
-
svg.append('g')
|
|
107
|
-
.attr('transform', `translate(${this.padding.left},0)`)
|
|
108
|
-
.call(yAxis)
|
|
109
|
-
.call((g) => g.append('text')
|
|
110
|
-
.attr('x', 0)
|
|
111
|
-
.attr('y', '1em')
|
|
112
|
-
.attr('fill', 'currentColor')
|
|
113
|
-
.attr('text-anchor', 'start')
|
|
114
|
-
.text(labels.x))
|
|
115
|
-
;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* @callback scale
|
|
120
|
-
* @property {number} n - A number inside the scale's domain
|
|
121
|
-
* @see https://d3js.org/d3-scale
|
|
122
|
-
*/
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* This object is a wrapper for two d3.scale objects, which can be used to
|
|
126
|
-
* convert coordinates from the graph space to this viewer viewport space.
|
|
127
|
-
* @typedef {Object} Viewport
|
|
128
|
-
* @property {scale} vx - A scale returning the x (horizontal) coordinate
|
|
129
|
-
* @property {scale} vy - A scale returning the y (vertical) coordinate
|
|
130
|
-
* @see https://d3js.org/d3-scale
|
|
131
|
-
*/
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Property returning this element viewport.
|
|
135
|
-
* The viewport is composed of two scale functions, which can be used to
|
|
136
|
-
* convert x (resp. y) to a number between 0 and the width (resp. height)
|
|
137
|
-
* of the viewer.
|
|
138
|
-
* @returns {Viewport} This viewer current viewport
|
|
139
|
-
*/
|
|
140
|
-
get viewport() {
|
|
141
|
-
const xMax = d3.max(this.items, (item) => this.getItemX(item));
|
|
142
|
-
const yMax = d3.max(this.items, (item) => this.getItemY(item));
|
|
143
|
-
// a scale is a function, domain is its input and range is its output
|
|
144
|
-
const vx = d3.scaleLinear()
|
|
145
|
-
.domain([0, xMax])
|
|
146
|
-
.range([this.padding.left, this.width-this.padding.right]);
|
|
147
|
-
const vy = d3.scaleLinear()
|
|
148
|
-
.domain([0, yMax])
|
|
149
|
-
.range([this.height-this.padding.top, this.padding.bottom]);
|
|
150
|
-
// y is inverted because having the origin (0;0) of the scatter graph
|
|
151
|
-
// in the bottom left corner feels more natural than the top left origin
|
|
152
|
-
// which is standard in SVG.
|
|
153
|
-
return { vx, vy };
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
getItemX(item) {
|
|
157
|
-
return this.getItemMetadata(item, this.pidx);
|
|
158
|
-
}
|
|
159
|
-
getItemY(item) {
|
|
160
|
-
return this.getItemMetadata(item, this.pidy);
|
|
161
|
-
}
|
|
162
|
-
getItemRadius(item) {
|
|
163
|
-
return 5;
|
|
164
|
-
}
|
|
165
|
-
getItemColor(item) {
|
|
166
|
-
return this.color;
|
|
167
|
-
}
|
|
168
|
-
getItemLabel(item) {
|
|
169
|
-
const name = this.getItemMetadata(item, this.label);
|
|
170
|
-
return name ? name : `Missing property '${this.label}'`;
|
|
171
|
-
}
|
|
172
|
-
getAxesLabels() {
|
|
173
|
-
const px = this.getProperty(this.pidx);
|
|
174
|
-
const py = this.getProperty(this.pidy);
|
|
175
|
-
return {
|
|
176
|
-
x: px ? px.name : `Missing property '${this.pidx}'`,
|
|
177
|
-
y: py ? py.name : `Missing property '${this.pidy}'`,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
getItemMetadata(item, pid) {
|
|
182
|
-
const found = item.metadata.find((m) => m.pid === pid);
|
|
183
|
-
return found ? found.value : null;
|
|
184
|
-
}
|
|
185
|
-
getProperty(pid) {
|
|
186
|
-
const found = this.properties.find((p) => p['@id'] === pid);
|
|
187
|
-
return found ? found : null;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
export { ScatterPlot };
|