UNPKG

20.3 kBHTMLView Raw
1<!-- Load c3.css -->
2<style>
3/*-- Chart --*/
4.c3 svg {
5 font: 10px sans-serif;
6 -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
7}
8
9.c3 path, .c3 line {
10 fill: none;
11 stroke: #000;
12}
13
14.c3 text {
15 -webkit-user-select: none;
16 -moz-user-select: none;
17 user-select: none;
18}
19
20.c3-legend-item-tile,
21.c3-xgrid-focus,
22.c3-ygrid,
23.c3-event-rect,
24.c3-bars path {
25 shape-rendering: crispEdges;
26}
27
28.c3-chart-arc path {
29 stroke: #fff;
30}
31
32.c3-chart-arc rect {
33 stroke: white;
34 stroke-width: 1;
35}
36
37.c3-chart-arc text {
38 fill: #fff;
39 font-size: 13px;
40}
41
42/*-- Axis --*/
43/*-- Grid --*/
44.c3-grid line {
45 stroke: #aaa;
46}
47
48.c3-grid text {
49 fill: #aaa;
50}
51
52.c3-xgrid, .c3-ygrid {
53 stroke-dasharray: 3 3;
54}
55
56/*-- Text on Chart --*/
57.c3-text.c3-empty {
58 fill: #808080;
59 font-size: 2em;
60}
61
62/*-- Line --*/
63.c3-line {
64 stroke-width: 1px;
65}
66
67/*-- Point --*/
68.c3-circle._expanded_ {
69 stroke-width: 1px;
70 stroke: white;
71}
72
73.c3-selected-circle {
74 fill: white;
75 stroke-width: 2px;
76}
77
78/*-- Bar --*/
79.c3-bar {
80 stroke-width: 0;
81}
82
83.c3-bar._expanded_ {
84 fill-opacity: 1;
85 fill-opacity: 0.75;
86}
87
88/*-- Focus --*/
89.c3-target.c3-focused {
90 opacity: 1;
91}
92
93.c3-target.c3-focused path.c3-line, .c3-target.c3-focused path.c3-step {
94 stroke-width: 2px;
95}
96
97.c3-target.c3-defocused {
98 opacity: 0.3 !important;
99}
100
101/*-- Region --*/
102.c3-region {
103 fill: steelblue;
104 fill-opacity: 0.1;
105}
106
107/*-- Brush --*/
108.c3-brush .extent {
109 fill-opacity: 0.1;
110}
111
112/*-- Select - Drag --*/
113/*-- Legend --*/
114.c3-legend-item {
115 font-size: 12px;
116}
117
118.c3-legend-item-hidden {
119 opacity: 0.15;
120}
121
122.c3-legend-background {
123 opacity: 0.75;
124 fill: white;
125 stroke: lightgray;
126 stroke-width: 1;
127}
128
129/*-- Title --*/
130.c3-title {
131 font: 14px sans-serif;
132}
133
134/*-- Tooltip --*/
135.c3-tooltip-container {
136 z-index: 10;
137}
138
139.c3-tooltip {
140 border-collapse: collapse;
141 border-spacing: 0;
142 background-color: #fff;
143 empty-cells: show;
144 -webkit-box-shadow: 7px 7px 12px -9px #777777;
145 -moz-box-shadow: 7px 7px 12px -9px #777777;
146 box-shadow: 7px 7px 12px -9px #777777;
147 opacity: 0.9;
148}
149
150.c3-tooltip tr {
151 border: 1px solid #CCC;
152}
153
154.c3-tooltip th {
155 background-color: #aaa;
156 font-size: 14px;
157 padding: 2px 5px;
158 text-align: left;
159 color: #FFF;
160}
161
162.c3-tooltip td {
163 font-size: 13px;
164 padding: 3px 6px;
165 background-color: #fff;
166 border-left: 1px dotted #999;
167}
168
169.c3-tooltip td > span {
170 display: inline-block;
171 width: 10px;
172 height: 10px;
173 margin-right: 6px;
174}
175
176.c3-tooltip td.value {
177 text-align: right;
178}
179
180/*-- Area --*/
181.c3-area {
182 stroke-width: 0;
183 opacity: 0.2;
184}
185
186/*-- Arc --*/
187.c3-chart-arcs-title {
188 dominant-baseline: middle;
189 font-size: 1.3em;
190}
191
192.c3-chart-arcs .c3-chart-arcs-background {
193 fill: #e0e0e0;
194 stroke: #FFF;
195}
196
197.c3-chart-arcs .c3-chart-arcs-gauge-unit {
198 fill: #000;
199 font-size: 16px;
200}
201
202.c3-chart-arcs .c3-chart-arcs-gauge-max {
203 fill: #777;
204}
205
206.c3-chart-arcs .c3-chart-arcs-gauge-min {
207 fill: #777;
208}
209
210.c3-chart-arc .c3-gauge-value {
211 fill: #000;
212 /* font-size: 28px !important;*/
213}
214
215.c3-chart-arc.c3-target g path {
216 opacity: 1;
217}
218
219.c3-chart-arc.c3-target.c3-focused g path {
220 opacity: 1;
221}
222
223/*-- Zoom --*/
224.c3-drag-zoom.enabled {
225 pointer-events: all !important;
226 visibility: visible;
227}
228
229.c3-drag-zoom.disabled {
230 pointer-events: none !important;
231 visibility: hidden;
232}
233
234.c3-drag-zoom .extent {
235 fill-opacity: 0.1;
236}
237
238</style>
239
240<!-- Load jQuery -->
241<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
242
243<!-- Load d3.js and c3.js -->
244<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.js" charset="utf-8"></script>
245<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.7/c3.js"></script>
246
247<!-- saveSVGtoPNG -->
248<script>
249(function() {
250 const out$ = typeof exports != 'undefined' && exports || typeof define != 'undefined' && {} || this || window;
251 if (typeof define !== 'undefined') define('save-svg-as-png', [], () => out$);
252
253 const xmlns = 'http://www.w3.org/2000/xmlns/';
254 const doctype = '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [<!ENTITY nbsp "&#160;">]>';
255 const urlRegex = /url\(["']?(.+?)["']?\)/;
256 const fontFormats = {
257 woff2: 'font/woff2',
258 woff: 'font/woff',
259 otf: 'application/x-font-opentype',
260 ttf: 'application/x-font-ttf',
261 eot: 'application/vnd.ms-fontobject',
262 sfnt: 'application/font-sfnt',
263 svg: 'image/svg+xml'
264 };
265
266 const isElement = obj => obj instanceof HTMLElement || obj instanceof SVGElement;
267 const requireDomNode = el => {
268 if (!isElement(el)) throw new Error(`an HTMLElement or SVGElement is required; got ${el}`);
269 };
270 const isExternal = url => url && url.lastIndexOf('http',0) === 0 && url.lastIndexOf(window.location.host) === -1;
271
272 const getFontMimeTypeFromUrl = fontUrl => {
273 const formats = Object.keys(fontFormats)
274 .filter(extension => fontUrl.indexOf(`.${extension}`) > 0)
275 .map(extension => fontFormats[extension]);
276 if (formats) return formats[0];
277 console.error(`Unknown font format for ${fontUrl}. Fonts may not be working correctly.`);
278 return 'application/octet-stream';
279 };
280
281 const arrayBufferToBase64 = buffer => {
282 let binary = '';
283 const bytes = new Uint8Array(buffer);
284 for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);
285 return window.btoa(binary);
286 }
287
288 const getDimension = (el, clone, dim) => {
289 const v =
290 (el.viewBox && el.viewBox.baseVal && el.viewBox.baseVal[dim]) ||
291 (clone.getAttribute(dim) !== null && !clone.getAttribute(dim).match(/%$/) && parseInt(clone.getAttribute(dim))) ||
292 el.getBoundingClientRect()[dim] ||
293 parseInt(clone.style[dim]) ||
294 parseInt(window.getComputedStyle(el).getPropertyValue(dim));
295 return typeof v === 'undefined' || v === null || isNaN(parseFloat(v)) ? 0 : v;
296 };
297
298 const getDimensions = (el, clone, width, height) => {
299 if (el.tagName === 'svg') return {
300 width: width || getDimension(el, clone, 'width'),
301 height: height || getDimension(el, clone, 'height')
302 };
303 else if (el.getBBox) {
304 const {x, y, width, height} = el.getBBox();
305 return {
306 width: x + width,
307 height: y + height
308 };
309 }
310 };
311
312 const reEncode = data =>
313 decodeURIComponent(
314 encodeURIComponent(data)
315 .replace(/%([0-9A-F]{2})/g, (match, p1) => {
316 const c = String.fromCharCode(`0x${p1}`);
317 return c === '%' ? '%25' : c;
318 })
319 );
320
321 const uriToBlob = uri => {
322 const byteString = window.atob(uri.split(',')[1]);
323 const mimeString = uri.split(',')[0].split(':')[1].split(';')[0]
324 const buffer = new ArrayBuffer(byteString.length);
325 const intArray = new Uint8Array(buffer);
326 for (let i = 0; i < byteString.length; i++) {
327 intArray[i] = byteString.charCodeAt(i);
328 }
329 return new Blob([buffer], {type: mimeString});
330 };
331
332 const query = (el, selector) => {
333 if (!selector) return;
334 try {
335 return el.querySelector(selector) || el.parentNode && el.parentNode.querySelector(selector);
336 } catch(err) {
337 console.warn(`Invalid CSS selector "${selector}"`, err);
338 }
339 };
340
341 const detectCssFont = (rule, href) => {
342 // Match CSS font-face rules to external links.
343 // @font-face {
344 // src: local('Abel'), url(https://fonts.gstatic.com/s/abel/v6/UzN-iejR1VoXU2Oc-7LsbvesZW2xOQ-xsNqO47m55DA.woff2);
345 // }
346 const match = rule.cssText.match(urlRegex);
347 const url = (match && match[1]) || '';
348 if (!url || url.match(/^data:/) || url === 'about:blank') return;
349 const fullUrl =
350 url.startsWith('../') ? `${href}/../${url}`
351 : url.startsWith('./') ? `${href}/.${url}`
352 : url;
353 return {
354 text: rule.cssText,
355 format: getFontMimeTypeFromUrl(fullUrl),
356 url: fullUrl
357 };
358 };
359
360 const inlineImages = el => Promise.all(
361 Array.from(el.querySelectorAll('image')).map(image => {
362 let href = image.getAttributeNS('http://www.w3.org/1999/xlink', 'href') || image.getAttribute('href');
363 if (!href) return Promise.resolve(null);
364 if (isExternal(href)) {
365 href += (href.indexOf('?') === -1 ? '?' : '&') + 't=' + new Date().valueOf();
366 }
367 return new Promise((resolve, reject) => {
368 const canvas = document.createElement('canvas');
369 const img = new Image();
370 img.crossOrigin = 'anonymous';
371 img.src = href;
372 img.onerror = () => reject(new Error(`Could not load ${href}`));
373 img.onload = () => {
374 canvas.width = img.width;
375 canvas.height = img.height;
376 canvas.getContext('2d').drawImage(img, 0, 0);
377 image.setAttributeNS('http://www.w3.org/1999/xlink', 'href', canvas.toDataURL('image/png'));
378 resolve(true);
379 };
380 });
381 })
382 );
383
384 const cachedFonts = {};
385 const inlineFonts = fonts => Promise.all(
386 fonts.map(font =>
387 new Promise((resolve, reject) => {
388 if (cachedFonts[font.url]) return resolve(cachedFonts[font.url]);
389
390 const req = new XMLHttpRequest();
391 req.addEventListener('load', () => {
392 // TODO: it may also be worth it to wait until fonts are fully loaded before
393 // attempting to rasterize them. (e.g. use https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet)
394 const fontInBase64 = arrayBufferToBase64(req.response);
395 const fontUri = font.text.replace(urlRegex, `url("data:${font.format};base64,${fontInBase64}")`)+'\n';
396 cachedFonts[font.url] = fontUri;
397 resolve(fontUri);
398 });
399 req.addEventListener('error', e => {
400 console.warn(`Failed to load font from: ${font.url}`, e);
401 cachedFonts[font.url] = null;
402 resolve(null);
403 });
404 req.addEventListener('abort', e => {
405 console.warn(`Aborted loading font from: ${font.url}`, e);
406 resolve(null);
407 });
408 req.open('GET', font.url);
409 req.responseType = 'arraybuffer';
410 req.send();
411 })
412 )
413 ).then(fontCss => fontCss.filter(x => x).join(''));
414
415 let cachedRules = null;
416 const styleSheetRules = () => {
417 if (cachedRules) return cachedRules;
418 return cachedRules = Array.from(document.styleSheets).map(sheet => {
419 try {
420 return {rules: sheet.cssRules, href: sheet.href};
421 } catch (e) {
422 console.warn(`Stylesheet could not be loaded: ${sheet.href}`, e);
423 return {};
424 }
425 });
426 };
427
428 const inlineCss = (el, options) => {
429 const {
430 selectorRemap,
431 modifyStyle,
432 modifyCss,
433 fonts
434 } = options || {};
435 const generateCss = modifyCss || ((selector, properties) => {
436 console.log(selectorRemap(selector));
437 const sel = selectorRemap ? selectorRemap(selector) : selector;
438 const props = modifyStyle ? modifyStyle(properties) : properties;
439 return `${sel}{${props}}\n`;
440 });
441 const css = [];
442 const detectFonts = typeof fonts === 'undefined';
443 const fontList = fonts || [];
444 styleSheetRules().forEach(({rules, href}) => {
445 if (!rules) return;
446 Array.from(rules).forEach(rule => {
447 if (typeof rule.style != 'undefined') {
448 if (query(el, rule.selectorText)) css.push(generateCss(rule.selectorText, rule.style.cssText));
449 else if (detectFonts && rule.cssText.match(/^@font-face/)) {
450 const font = detectCssFont(rule, href);
451 if (font) fontList.push(font);
452 } else css.push(rule.cssText);
453 }
454 });
455 });
456
457 return inlineFonts(fontList).then(fontCss => css.join('\n') + fontCss);
458 };
459
460 out$.prepareSvg = (el, options, done) => {
461 requireDomNode(el);
462 const {
463 left = 0,
464 top = 0,
465 width: w,
466 height: h,
467 scale = 1,
468 responsive = false,
469 } = options || {};
470
471 return inlineImages(el).then(() => {
472 let clone = el.cloneNode(true);
473 const {backgroundColor = 'transparent'} = options || {};
474 clone.style.backgroundColor = backgroundColor;
475 const {width, height} = getDimensions(el, clone, w, h);
476
477 if (el.tagName !== 'svg') {
478 if (el.getBBox) {
479 clone.setAttribute('transform', clone.getAttribute('transform').replace(/translate\(.*?\)/, ''));
480 const svg = document.createElementNS('http://www.w3.org/2000/svg','svg');
481 svg.appendChild(clone);
482 clone = svg;
483 } else {
484 console.error('Attempted to render non-SVG element', el);
485 return;
486 }
487 }
488
489 clone.setAttribute('version', '1.1');
490 clone.setAttribute('viewBox', [left, top, width, height].join(' '));
491 if (!clone.getAttribute('xmlns')) clone.setAttributeNS(xmlns, 'xmlns', 'http://www.w3.org/2000/svg');
492 if (!clone.getAttribute('xmlns:xlink')) clone.setAttributeNS(xmlns, 'xmlns:xlink', 'http://www.w3.org/1999/xlink');
493
494 if (responsive) {
495 clone.removeAttribute('width');
496 clone.removeAttribute('height');
497 clone.setAttribute('preserveAspectRatio', 'xMinYMin meet');
498 } else {
499 clone.setAttribute('width', width * scale);
500 clone.setAttribute('height', height * scale);
501 }
502
503 Array.from(clone.querySelectorAll('foreignObject > *')).forEach(foreignObject => {
504 if (!foreignObject.getAttribute('xmlns'))
505 foreignObject.setAttributeNS(xmlns, 'xmlns', 'http://www.w3.org/1999/xhtml');
506 });
507
508 return inlineCss(el, options).then(css => {
509 const style = document.createElement('style');
510 style.setAttribute('type', 'text/css');
511 style.innerHTML = `<![CDATA[\n${css}\n]]>`;
512
513 const defs = document.createElement('defs');
514 defs.appendChild(style);
515 clone.insertBefore(defs, clone.firstChild);
516
517 const outer = document.createElement('div');
518 outer.appendChild(clone);
519 const src = outer.innerHTML.replace(/NS\d+:href/gi, 'xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href');
520
521 if (typeof done === 'function') done(src, width, height);
522 else return {src, width, height};
523 });
524 });
525 };
526
527 out$.svgAsDataUri = (el, options, done) => {
528 requireDomNode(el);
529 const result = out$.prepareSvg(el, options)
530 .then(({src}) => `data:image/svg+xml;base64,${window.btoa(reEncode(doctype+src))}`);
531 if (typeof done === 'function') return result.then(done);
532 return result;
533 };
534
535 out$.svgAsPngUri = (el, options, done) => {
536 requireDomNode(el);
537 const {
538 encoderType = 'image/png',
539 encoderOptions = 0.8,
540 canvg
541 } = options || {};
542
543 const convertToPng = ({src, width, height}) => {
544 const canvas = document.createElement('canvas');
545 const context = canvas.getContext('2d');
546 const pixelRatio = window.devicePixelRatio || 1;
547
548 canvas.width = width * pixelRatio;
549 canvas.height = height * pixelRatio;
550 canvas.style.width = `${canvas.width}px`;
551 canvas.style.height = `${canvas.height}px`;
552 context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
553
554 if (canvg) canvg(canvas, src);
555 else context.drawImage(src, 0, 0);
556
557 let png;
558 try {
559 png = canvas.toDataURL(encoderType, encoderOptions);
560 } catch (e) {
561 if ((typeof SecurityError !== 'undefined' && e instanceof SecurityError) || e.name === 'SecurityError') {
562 console.error('Rendered SVG images cannot be downloaded in this browser.');
563 return;
564 } else throw e;
565 }
566 if (typeof done === 'function') done(png);
567 return Promise.resolve(png);
568 }
569
570 if (canvg) return out$.prepareSvg(el, options).then(convertToPng);
571 else return out$.svgAsDataUri(el, options).then(uri => {
572 return new Promise((resolve, reject) => {
573 const image = new Image();
574 image.onload = () => resolve(convertToPng({
575 src: image,
576 width: image.width,
577 height: image.height
578 }));
579 image.onerror = () => {
580 reject(`There was an error loading the data URI as an image on the following SVG\n${window.atob(uri.slice(26))}Open the following link to see browser's diagnosis\n${uri}`);
581 }
582 image.src = uri;
583 })
584 });
585 };
586
587 out$.download = (name, uri) => {
588 if (navigator.msSaveOrOpenBlob) navigator.msSaveOrOpenBlob(uriToBlob(uri), name);
589 else {
590 const saveLink = document.createElement('a');
591 if ('download' in saveLink) {
592 saveLink.download = name;
593 saveLink.style.display = 'none';
594 document.body.appendChild(saveLink);
595 try {
596 const blob = uriToBlob(uri);
597 const url = URL.createObjectURL(blob);
598 saveLink.href = url;
599 saveLink.onclick = () => requestAnimationFrame(() => URL.revokeObjectURL(url));
600 } catch (e) {
601 console.warn('This browser does not support object URLs. Falling back to string URL.');
602 saveLink.href = uri;
603 }
604 saveLink.click();
605 document.body.removeChild(saveLink);
606 }
607 else {
608 window.open(uri, '_temp', 'menubar=no,toolbar=no,status=no');
609 }
610 }
611 };
612
613 out$.saveSvg = (el, name, options) => {
614 requireDomNode(el);
615 out$.svgAsDataUri(el, options || {}, uri => out$.download(name, uri));
616 };
617
618 out$.saveSvgAsPng = (el, name, options) => {
619 requireDomNode(el);
620 out$.svgAsPngUri(el, options || {}, uri => out$.download(name, uri));
621 };
622})();
623
624</script>
625
626<!-- Generate cart -->
627<script>
628 $(document).ready(function () {
629 var chart = c3.generate({
630 bindto: '#chart',
631 data: {
632 columns: [
633 ['data1',
634 0.858926142,
635 -3.111590247,
636 -3.94420832,
637 -4.175615293,
638 -3.740784455,
639 -3.416939753,
640 -1.721508409,
641 0.187249078,
642 -0.17687197,
643 -3.234997643,
644 -4.220987109,
645 -0.956783273,
646 -0.033680654,
647 -0.140363168,
648 0.326102198,
649 0.639569773,
650 0.819556481,
651 0.351295891,
652 0.158211084
653 ],
654 ['data2',
655 4.013337337,
656 -0.390845436,
657 -0.84064409,
658 -0.45909649,
659 -0.223673085,
660 0.515092083,
661 1.441340134,
662 2.480247904,
663 2.052278238,
664 1.361221601,
665 0.722628463,
666 1.74097416,
667 2.109832697,
668 2.025055114,
669 2.192805826,
670 2.179848926,
671 1.976133973,
672 1.302580393,
673 0.883748718
674 ],
675 ['data3',
676 40.34831418,
677 38.37374131,
678 37.90326304,
679 38.22839717,
680 37.36657829,
681 37.31726398,
682 37.57459522,
683 37.63157371,
684 37.94299968,
685 38.33543337,
686 37.00018604,
687 37.46555832,
688 38.09271062,
689 38.45519135,
690 38.5445274,
691 38.77569391,
692 39.32543166,
693 39.36444162,
694 39.23532091
695 ]
696 ],
697 names: {
698 data1: 'Finanzierungssaldo',
699 data2: 'Struktureller Primärsaldo',
700 data3: 'Abgabenquote (rechte Skala)'
701 },
702 axes: {
703 data3: 'y2' // ADD
704
705 }
706 },
707 axis: {
708 y: {
709 label: { // ADD
710 text: '%',
711 position: 'inner-top',
712 },
713 max: 6,
714 min: -6
715 },
716 y2: {
717 show: true,
718 label: { // ADD
719 text: '%',
720 position: 'inner-top'
721 },
722 max: 41,
723 min: 35
724 }
725 }
726 });
727 d3.select(".c3-axis-y-label").attr("transform", "translate(20,0)");
728 d3.select(".c3-axis-y2-label").attr("transform", "translate(0,17)");
729
730 prepareSvg(document.querySelector('svg'), {selectorRemap: s => s.replace(/\.c3 /g, '')})
731 .then(svg => document.getElementById('svg').innerHTML = svg.src);
732 svgAsPngUri(document.querySelector('svg'), {selectorRemap: s => s.replace(/\.c3 /g, '')})
733 .then(uri => document.getElementById('image').setAttribute('src', uri));
734
735 });
736</script>
737
738<div id="chart" class="morechart"></div>
739<div id="svg"></div>
740<div><img id="image" /></div>