[export] export to svg working

This commit is contained in:
Prateeksha Singh 2018-04-17 01:08:32 +05:30
parent e28c564639
commit 8e45278303
19 changed files with 8784 additions and 79 deletions

View File

@ -1276,6 +1276,8 @@ function runSMILAnimation(parent, svgElement, elementsToAnimate) {
}, REPLACE_ALL_NEW_DUR);
}
const CSSTEXT = ".chart-container{position:relative;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Roboto','Oxygen','Ubuntu','Cantarell','Fira Sans','Droid Sans','Helvetica Neue',sans-serif}.chart-container .axis,.chart-container .chart-label{fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.chart-container .dataset-units circle{stroke:#fff;stroke-width:2}.chart-container .dataset-units path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container .dataset-path{stroke-width:2px}.chart-container .path-group path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container line.dashed{stroke-dasharray:5,3}.chart-container .axis-line .specific-value{text-anchor:start}.chart-container .axis-line .y-line{text-anchor:end}.chart-container .axis-line .x-line{text-anchor:middle}.graph-svg-tip{position:absolute;z-index:99999;padding:10px;font-size:12px;color:#959da5;text-align:center;background:rgba(0,0,0,.8);border-radius:3px}.graph-svg-tip ul{padding-left:0;display:flex}.graph-svg-tip ol{padding-left:0;display:flex}.graph-svg-tip ul.data-point-list li{min-width:90px;flex:1;font-weight:600}.graph-svg-tip strong{color:#dfe2e5;font-weight:600}.graph-svg-tip .svg-pointer{position:absolute;height:5px;margin:0 0 0 -5px;content:' ';border:5px solid transparent;border-top-color:rgba(0,0,0,.8)}.graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.graph-svg-tip.comparison li{display:inline-block;padding:5px 10px}";
class BaseChart {
constructor(parent, options) {
@ -1599,6 +1601,42 @@ class BaseChart {
window.removeEventListener('resize', () => this.draw(true));
window.removeEventListener('orientationchange', () => this.draw(true));
}
export() {
let chartSvg = this.prepareForExport();
this.downloadFile(this.title || 'Chart', [chartSvg]);
}
downloadFile(filename, data) {
var a = document.createElement('a');
a.style = "display: none";
var blob = new Blob(data, {type: "image/svg+xml; charset=utf-8"});
var url = window.URL.createObjectURL(blob);
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
setTimeout(function(){
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 300);
}
prepareForExport() {
let clone = this.svg.cloneNode(true);
clone.classList.add('chart-container');
clone.setAttribute('xmlns', "http://www.w3.org/2000/svg");
clone.setAttribute('xmlns:xlink', "http://www.w3.org/1999/xlink");
let styleEl = $.create('style', {
'innerHTML': CSSTEXT
});
clone.insertBefore(styleEl, clone.firstChild);
let container = $.create('div');
container.appendChild(clone);
return container.innerHTML;
}
}
class AggregationChart extends BaseChart {
@ -3594,6 +3632,8 @@ class AxisChart extends BaseChart {
}
const chartTypes = {
bar: AxisChart,
line: AxisChart,
// multiaxis: MultiAxisChart,
percentage: PercentageChart,
heatmap: Heatmap,
@ -3601,13 +3641,7 @@ const chartTypes = {
};
function getChartByType(chartType = 'line', parent, options) {
if(chartType === 'line') {
options.type = 'line';
return new AxisChart(parent, options);
} else if (chartType === 'bar') {
options.type = 'bar';
return new AxisChart(parent, options);
} else if (chartType === 'axis-mixed') {
if (chartType === 'axis-mixed') {
options.type = 'line';
return new AxisChart(parent, options);
}

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
.chart-container{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;position:relative}.chart-container .axis,.chart-container .chart-label{fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.chart-container .dataset-units circle{stroke:#fff;stroke-width:2}.chart-container .dataset-units path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container .dataset-path{stroke-width:2px}.chart-container .path-group path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container line.dashed{stroke-dasharray:5,3}.chart-container .axis-line .specific-value{text-anchor:start}.chart-container .axis-line .y-line{text-anchor:end}.chart-container .axis-line .x-line{text-anchor:middle}.graph-svg-tip{position:absolute;z-index:1;padding:10px;font-size:12px;color:#959da5;text-align:center;background:rgba(0,0,0,.8);border-radius:3px}.graph-svg-tip ol,.graph-svg-tip ul{padding-left:0;display:-webkit-box;display:-ms-flexbox;display:flex}.graph-svg-tip ul.data-point-list li{min-width:90px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-weight:600}.graph-svg-tip strong{color:#dfe2e5;font-weight:600}.graph-svg-tip .svg-pointer{position:absolute;height:5px;margin:0 0 0 -5px;content:" ";border:5px solid transparent;border-top-color:rgba(0,0,0,.8)}.graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.graph-svg-tip.comparison li{display:inline-block;padding:5px 10px}
.chart-container{position:relative;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif}.chart-container .axis,.chart-container .chart-label{fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.chart-container .dataset-units circle{stroke:#fff;stroke-width:2}.chart-container .dataset-units path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container .dataset-path{stroke-width:2px}.chart-container .path-group path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container line.dashed{stroke-dasharray:5,3}.chart-container .axis-line .specific-value{text-anchor:start}.chart-container .axis-line .y-line{text-anchor:end}.chart-container .axis-line .x-line{text-anchor:middle}.graph-svg-tip{position:absolute;z-index:1;padding:10px;font-size:12px;color:#959da5;text-align:center;background:rgba(0,0,0,.8);border-radius:3px}.graph-svg-tip ol,.graph-svg-tip ul{padding-left:0;display:-webkit-box;display:-ms-flexbox;display:flex}.graph-svg-tip ul.data-point-list li{min-width:90px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-weight:600}.graph-svg-tip strong{color:#dfe2e5;font-weight:600}.graph-svg-tip .svg-pointer{position:absolute;height:5px;margin:0 0 0 -5px;content:" ";border:5px solid transparent;border-top-color:rgba(0,0,0,.8)}.graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.graph-svg-tip.comparison li{display:inline-block;padding:5px 10px}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -88,6 +88,8 @@ Array.prototype.slice.call(
});
});
aggrChart.export();
// Update values chart
// ================================================================================
let updateDataAllLabels = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", "Mon", "Tue",
@ -105,7 +107,7 @@ let getUpdateData = (source_array, length=10) => {
return indices.map((index) => source_array[index]);
};
let update_data = {
let updateData = {
labels: getUpdateData(updateDataAllLabels),
datasets: [{
"values": getUpdateData(updateDataAllValues)
@ -126,8 +128,8 @@ let update_data = {
],
};
let update_chart = new Chart("#chart-update", {
data: update_data,
let updateChart = new Chart("#chart-update", {
data: updateData,
type: 'line',
height: 250,
colors: ['#ff6c03'],
@ -137,9 +139,9 @@ let update_chart = new Chart("#chart-update", {
},
});
let chart_update_buttons = document.querySelector('.chart-update-buttons');
let chartUpdateButtons = document.querySelector('.chart-update-buttons');
chart_update_buttons.querySelector('[data-update="random"]').addEventListener("click", () => {
chartUpdateButtons.querySelector('[data-update="random"]').addEventListener("click", () => {
shuffle(updateDataAllIndices);
let value = getRandom();
let start = getRandom();
@ -162,19 +164,19 @@ chart_update_buttons.querySelector('[data-update="random"]').addEventListener("c
},
],
};
update_chart.update(data);
updateChart.update(data);
});
chart_update_buttons.querySelector('[data-update="add"]').addEventListener("click", () => {
let index = update_chart.state.datasetLength; // last index to add
chartUpdateButtons.querySelector('[data-update="add"]').addEventListener("click", () => {
let index = updateChart.state.datasetLength; // last index to add
if(index >= updateDataAllIndices.length) return;
update_chart.addDataPoint(
updateChart.addDataPoint(
updateDataAllLabels[index], [updateDataAllValues[index]]
);
});
chart_update_buttons.querySelector('[data-update="remove"]').addEventListener("click", () => {
update_chart.removeDataPoint();
chartUpdateButtons.querySelector('[data-update="remove"]').addEventListener("click", () => {
updateChart.removeDataPoint();
});
// Trends Chart

View File

@ -49,12 +49,6 @@ var HEATMAP_COLORS_YELLOW = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001
// Universal constants
/**
* Returns the value of a number upto 2 decimal places.
* @param {Number} d Any number
*/
/**
* Returns whether or not two given arrays are equal.
* @param {Array} arr1 First array
@ -124,6 +118,7 @@ var MONTH_NAMES_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
// https://stackoverflow.com/a/11252167/6495043
function clone(date) {
@ -164,8 +159,6 @@ function addDays(date, numberOfDays) {
date.setDate(date.getDate() + numberOfDays);
}
// Composite Chart
// ================================================================================
var reportCountList = [152, 222, 199, 287, 534, 709, 1179, 1256, 1632, 1856, 1850];
var lineCompositeData = {
@ -385,6 +378,8 @@ Array.prototype.slice.call(document.querySelectorAll('.aggr-type-buttons button'
});
});
aggrChart.export();
// Update values chart
// ================================================================================
var updateDataAllLabels = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", "Mon"];
@ -408,7 +403,7 @@ var getUpdateData = function getUpdateData(source_array) {
});
};
var update_data = {
var updateData = {
labels: getUpdateData(updateDataAllLabels),
datasets: [{
"values": getUpdateData(updateDataAllValues)
@ -425,8 +420,8 @@ var update_data = {
}]
};
var update_chart = new Chart("#chart-update", {
data: update_data,
var updateChart = new Chart("#chart-update", {
data: updateData,
type: 'line',
height: 250,
colors: ['#ff6c03'],
@ -436,9 +431,9 @@ var update_chart = new Chart("#chart-update", {
}
});
var chart_update_buttons = document.querySelector('.chart-update-buttons');
var chartUpdateButtons = document.querySelector('.chart-update-buttons');
chart_update_buttons.querySelector('[data-update="random"]').addEventListener("click", function () {
chartUpdateButtons.querySelector('[data-update="random"]').addEventListener("click", function () {
shuffle(updateDataAllIndices);
var value = getRandom();
var start = getRandom();
@ -457,17 +452,17 @@ chart_update_buttons.querySelector('[data-update="random"]').addEventListener("c
end: end
}]
};
update_chart.update(data);
updateChart.update(data);
});
chart_update_buttons.querySelector('[data-update="add"]').addEventListener("click", function () {
var index = update_chart.state.datasetLength; // last index to add
chartUpdateButtons.querySelector('[data-update="add"]').addEventListener("click", function () {
var index = updateChart.state.datasetLength; // last index to add
if (index >= updateDataAllIndices.length) return;
update_chart.addDataPoint(updateDataAllLabels[index], [updateDataAllValues[index]]);
updateChart.addDataPoint(updateDataAllLabels[index], [updateDataAllValues[index]]);
});
chart_update_buttons.querySelector('[data-update="remove"]').addEventListener("click", function () {
update_chart.removeDataPoint();
chartUpdateButtons.querySelector('[data-update="remove"]').addEventListener("click", function () {
updateChart.removeDataPoint();
});
// Trends Chart

File diff suppressed because one or more lines are too long

View File

@ -100,6 +100,7 @@
<button type="button" class="btn btn-sm btn-secondary" data-type='pie'>Pie Chart</button>
<button type="button" class="btn btn-sm btn-secondary" data-type='percentage'>Percentage Chart</button>
</div>
<button type="button" class="btn btn-sm btn-tertiary" data-type='export'>Export</button>
<!-- <p class="text-muted">
<a target="_blank" href="http://www.storytellingwithdata.com/blog/2011/07/death-to-pie-charts">Why Percentage?</a>
</p> -->

8615
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -33,16 +33,22 @@
},
"homepage": "https://github.com/frappe/charts#readme",
"devDependencies": {
"autoprefixer": "^8.2.0",
"babel-core": "^6.26.0",
"babel-plugin-external-helpers": "^6.22.0",
"babel-preset-env": "^1.6.1",
"babel-preset-latest": "^6.24.1",
"clean-css": "^4.1.11",
"cssnano": "^3.10.0",
"eslint": "^4.18.2",
"fs": "0.0.1-security",
"livereload": "^0.6.3",
"node-sass": "^4.7.2",
"npm-run-all": "^4.1.1",
"postcss": "^6.0.21",
"postcss-cssnext": "^3.0.2",
"postcss-nested": "^2.1.2",
"precss": "^3.1.2",
"rollup": "^0.50.0",
"rollup-plugin-babel": "^3.0.2",
"rollup-plugin-eslint": "^4.0.0",
@ -51,10 +57,7 @@
"rollup-plugin-replace": "^2.0.0",
"rollup-plugin-uglify": "^2.0.1",
"rollup-plugin-uglify-es": "0.0.1",
"rollup-watch": "^4.3.1",
"eslint": "^4.18.2"
"rollup-watch": "^4.3.1"
},
"dependencies": {
}
"dependencies": {}
}

View File

@ -1,17 +1,42 @@
import pkg from './package.json';
// Rollup plugins
import babel from 'rollup-plugin-babel';
import eslint from 'rollup-plugin-eslint';
import replace from 'rollup-plugin-replace';
import uglify from 'rollup-plugin-uglify-es';
import sass from 'node-sass';
import postcss from 'rollup-plugin-postcss';
// PostCSS plugins
import postcssPlugin from 'rollup-plugin-postcss';
import nested from 'postcss-nested';
import cssnext from 'postcss-cssnext';
import cssnano from 'cssnano';
import pkg from './package.json';
import postcss from 'postcss';
import precss from 'precss';
import CleanCSS from 'clean-css';
import autoprefixer from 'autoprefixer';
import fs from 'fs';
import { HEATMAP_LEFT_MARGIN } from './src/js/utils/constants';
fs.readFile('src/css/charts.scss', (err, css) => {
postcss([precss, autoprefixer])
.process(css, { from: 'src/css/charts.scss', to: 'src/css/charts.css' })
.then(result => {
let options = {
level: {
1: {
removeQuotes: false,
}
}
}
let output = new CleanCSS(options).minify(result.css);
let res = JSON.stringify(output.styles).replace(/"/g, "'");
let js = `export const CSSTEXT = "${res.slice(1, -1)}";`;
fs.writeFile('src/css/chartsCss.js', js);
});
});
export default [
{
@ -29,7 +54,7 @@ export default [
],
name: 'frappe',
plugins: [
postcss({
postcssPlugin({
preprocessor: (content, id) => new Promise((resolve, reject) => {
const result = sass.renderSync({ file: id })
resolve({ code: result.css.toString() })
@ -43,7 +68,7 @@ export default [
}),
eslint({
exclude: [
'src/scss/**'
'src/css/**'
]
}),
babel({
@ -67,7 +92,7 @@ export default [
],
name: 'frappe',
plugins: [
postcss({
postcssPlugin({
preprocessor: (content, id) => new Promise((resolve, reject) => {
const result = sass.renderSync({ file: id })
resolve({ code: result.css.toString() })
@ -81,7 +106,7 @@ export default [
}),
eslint({
exclude: [
'src/scss/**'
'src/css/**'
]
}),
babel({
@ -106,7 +131,7 @@ export default [
}
],
plugins: [
postcss({
postcssPlugin({
preprocessor: (content, id) => new Promise((resolve, reject) => {
const result = sass.renderSync({ file: id })
resolve({ code: result.css.toString() })
@ -120,7 +145,7 @@ export default [
}),
eslint({
exclude: [
'src/scss/**',
'src/css/**',
]
}),
babel({
@ -142,7 +167,7 @@ export default [
}
],
plugins: [
postcss({
postcssPlugin({
preprocessor: (content, id) => new Promise((resolve, reject) => {
const result = sass.renderSync({ file: id })
resolve({ code: result.css.toString() })
@ -157,7 +182,7 @@ export default [
}),
eslint({
exclude: [
'src/scss/**',
'src/css/**',
]
}),
replace({

View File

@ -1,10 +1,10 @@
.chart-container {
// https://www.smashingmagazine.com/2015/11/using-system-ui-fonts-practical-guide/
font-family: -apple-system, BlinkMacSystemFont,
"Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell",
"Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
position: relative; /* for absolutely positioned tooltip */
position: relative;
/* https://www.smashingmagazine.com/2015/11/using-system-ui-fonts-practical-guide/ */
font-family: -apple-system, BlinkMacSystemFont,
'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell',
'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
.axis, .chart-label {
fill: #555b51;
@ -36,13 +36,9 @@
}
}
line.dashed {
stroke-dasharray: 5,3;
stroke-dasharray: 5, 3;
}
.axis-line {
// &.x-axis-label {
// display: block;
// }
// TODO: hack dy attr to be settable via styles
.specific-value {
text-anchor: start;
}
@ -87,7 +83,7 @@
position: absolute;
height: 5px;
margin: 0 0 0 -5px;
content: " ";
content: ' ';
border: 5px solid transparent;
border-top-color: rgba(0, 0, 0, 0.8);
}

1
src/css/chartsCss.js Normal file
View File

@ -0,0 +1 @@
export const CSSTEXT = ".chart-container{position:relative;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Roboto','Oxygen','Ubuntu','Cantarell','Fira Sans','Droid Sans','Helvetica Neue',sans-serif}.chart-container .axis,.chart-container .chart-label{fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.chart-container .dataset-units circle{stroke:#fff;stroke-width:2}.chart-container .dataset-units path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container .dataset-path{stroke-width:2px}.chart-container .path-group path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container line.dashed{stroke-dasharray:5,3}.chart-container .axis-line .specific-value{text-anchor:start}.chart-container .axis-line .y-line{text-anchor:end}.chart-container .axis-line .x-line{text-anchor:middle}.graph-svg-tip{position:absolute;z-index:99999;padding:10px;font-size:12px;color:#959da5;text-align:center;background:rgba(0,0,0,.8);border-radius:3px}.graph-svg-tip ul{padding-left:0;display:flex}.graph-svg-tip ol{padding-left:0;display:flex}.graph-svg-tip ul.data-point-list li{min-width:90px;flex:1;font-weight:600}.graph-svg-tip strong{color:#dfe2e5;font-weight:600}.graph-svg-tip .svg-pointer{position:absolute;height:5px;margin:0 0 0 -5px;content:' ';border:5px solid transparent;border-top-color:rgba(0,0,0,.8)}.graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.graph-svg-tip.comparison li{display:inline-block;padding:5px 10px}";

View File

@ -1,4 +1,4 @@
import '../scss/charts.scss';
import '../css/charts.scss';
// import MultiAxisChart from './charts/MultiAxisChart';
import PercentageChart from './charts/PercentageChart';
@ -7,6 +7,8 @@ import Heatmap from './charts/Heatmap';
import AxisChart from './charts/AxisChart';
const chartTypes = {
bar: AxisChart,
line: AxisChart,
// multiaxis: MultiAxisChart,
percentage: PercentageChart,
heatmap: Heatmap,
@ -14,13 +16,7 @@ const chartTypes = {
};
function getChartByType(chartType = 'line', parent, options) {
if(chartType === 'line') {
options.type = 'line';
return new AxisChart(parent, options);
} else if (chartType === 'bar') {
options.type = 'bar';
return new AxisChart(parent, options);
} else if (chartType === 'axis-mixed') {
if (chartType === 'axis-mixed') {
options.type = 'line';
return new AxisChart(parent, options);
}

View File

@ -7,6 +7,7 @@ import { BASE_CHART_TOP_MARGIN, BASE_CHART_LEFT_MARGIN,
import { getColor, isValidColor } from '../utils/colors';
import { runSMILAnimation } from '../utils/animation';
import { Chart } from '../chart';
import { CSSTEXT } from '../../css/chartsCss';
export default class BaseChart {
constructor(parent, options) {
@ -331,4 +332,40 @@ export default class BaseChart {
window.removeEventListener('resize', () => this.draw(true));
window.removeEventListener('orientationchange', () => this.draw(true));
}
export() {
let chartSvg = this.prepareForExport();
this.downloadFile(this.title || 'Chart', [chartSvg]);
}
downloadFile(filename, data) {
var a = document.createElement('a');
a.style = "display: none";
var blob = new Blob(data, {type: "image/svg+xml; charset=utf-8"});
var url = window.URL.createObjectURL(blob);
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
setTimeout(function(){
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 300);
}
prepareForExport() {
let clone = this.svg.cloneNode(true);
clone.classList.add('chart-container');
clone.setAttribute('xmlns', "http://www.w3.org/2000/svg");
clone.setAttribute('xmlns:xlink', "http://www.w3.org/1999/xlink");
let styleEl = $.create('style', {
'innerHTML': CSSTEXT
});
clone.insertBefore(styleEl, clone.firstChild);
let container = $.create('div');
container.appendChild(clone);
return container.innerHTML;
}
}