JSXGraph in Moodle Formulas with two boards: Binding to input fields not working - moodle

I have created a Moodle Formulas questions in the field of kinematics with two boards. While I managed to get simpler questions with only one board to work flawlessly, the problem with this question is that the bound values are not inserted into formula's input entry fields. Consequently, the student cannot submit an answer because, effectively, nothing has been filled out. The rest of the question works though, as can be seen when the correct answers are filled in the question's preview.
I provide a Moodle XML file to make it easier to reproduce the problem: questions_formulas_JSXGraph_2boards.xml
You need a current version of Moodle with JSXGraph filter and question type Formulas installed.
The main JSXGraph code is this:
<jsxgraph width="400" height="300" numberOfBoards="2" ext_formulas>
// JavaScript code to create the construction.
var jsxCode = function (question) {
// Import final coordinates after submission
var x0={x0};
var t1,t2,t3 , v1,v2,v3 , x1,x2,x3;
[t1,t2,t3 , v1,v2,v3 , x1,x2,x3] =
question.getAllValues([1,2,3 , 1,2,3 , x0,x0,x0 ]);
JXG.Options.point.infoboxDigits = 1;
JXG.Options.point.snapSizeX = 1;
JXG.Options.point.snapSizeY = 0.1;
// Create boards
var brd0 = JXG.JSXGraph.initBoard(BOARDID0, {
boundingbox: [-1, 11, 12, -11], axis:true,
defaultAxes: {
x: {withLabel: true, name: 't in s',
label: {position: 'rt', offset: [-0, 15], anchorX: 'right'} },
y: {withLabel:true, name: 'x in m',
label: {position: 'rt', offset: [+15, -0]} } },
showCopyright: false, showNavigation: false
});
var brd1 = JXG.JSXGraph.initBoard(BOARDID1, {
boundingbox: [-1, 3.5, 12, -3.5], axis:true,
defaultAxes: {
x: {withLabel: true, name: 't in s',
label: {position: 'rt', offset: [-0, 15], anchorX: 'right'} },
y: {withLabel:true, name: 'v_x in m/s',
label: {position: 'rt', offset: [+15, -0]} } },
showCopyright: false, showNavigation: false
});
// Board brd0 needs to be updated when changes in brd1 occur
brd1.addChild(brd0);
// Attributes for points and lines
function attrPfix(addAttr={}) {
const attr = {fixed: true, visible: false, withLabel: false};
return { ...attr, ...addAttr};
}
function attrPmov(addAttr={}) {
const attr = {fixed: question.isSolved, snapToGrid: true, withLabel: false};
return { ...attr, ...addAttr};
}
function attrPsma(addAttr={}) {
const attr = {visible: true, withLabel: false, color:'#4285F4', size: 1};
return { ...attr, ...addAttr};
}
const attrLine = {borders: {strokeColor:'#4285F4', strokeWidth: 3} };
const attrGlid = {visible:false};
// Define lines and points on brd1
brd1.suspendUpdate();
var lV0 = brd1.create('segment', [[0,-10], [0,10]], {visible:false}),
lV3 = brd1.create('segment', [[-10,0], [20,0]], {visible:false});
var pV0 = brd1.create('glider', [0, v1, lV0], attrPmov({name: "pV0"}) ),
pV1 = brd1.create('point', [t1, v2], attrPmov({name: "pV1"}) ),
pV2 = brd1.create('point', [t2, v3], attrPmov({name: "pV2"}) ),
pV3 = brd1.create('glider', [t3, 0, lV3], attrPmov({name: "pV3"}) ),
pV01 = brd1.create('point', ["X(pV1)", "Y(pV0)"], attrPsma() ),
pV12 = brd1.create('point', ["X(pV2)", "Y(pV1)"], attrPsma() ),
pV23 = brd1.create('point', ["X(pV3)", "Y(pV2)"], attrPsma() ) ;
brd1.create('polygonalchain', [ pV0, pV01, pV1, pV12, pV2, pV23, pV3 ], attrLine);
brd1.unsuspendUpdate();
// Define lines and points on brd1
// Q: Is it necessary/beneficial/wrong to suspendUpdate here?
brd0.suspendUpdate();
var lX1 = brd0.create('line', [[function(){return pV1.X();},-10], [function(){return pV1.X();},10]], attrGlid),
lX2 = brd0.create('line', [[function(){return pV2.X();},-10], [function(){return pV2.X();},10]], attrGlid),
lX3 = brd0.create('line', [[function(){return pV3.X();},-10], [function(){return pV3.X();},10]], attrGlid);
var pX0 = brd0.create('point', [0, x0], attrPsma({fixed: true}) ),
pX1 = brd0.create('glider', [t1, x1, lX1], attrPmov({face: 'diamond'}) ),
pX2 = brd0.create('glider', [t2, x2, lX2], attrPmov({face: 'diamond'}) ),
pX3 = brd0.create('glider', [t3, x3, lX3], attrPmov({face: 'diamond'}) );
brd0.create('polygonalchain', [ pX0, pX1, pX2, pX3 ], attrLine);
brd0.unsuspendUpdate();
// Q: Are these updates necessary?
brd0.update();
brd1.update();
// Whenever the construction is altered the values of the points are sent to formulas.
question.bindInput(0, () => { return pV1.X(); });
question.bindInput(1, () => { return PV2.X(); });
question.bindInput(2, () => { return pV3.X(); });
question.bindInput(3, () => { return pV1.Y(); });
question.bindInput(4, () => { return pV2.Y(); });
question.bindInput(5, () => { return PV3.Y(); });
question.bindInput(6, () => { return pX1.Y(); });
question.bindInput(7, () => { return pX2.Y(); });
question.bindInput(8, () => { return pX3.Y(); });
};
// Execute the JavaScript code.
new JSXQuestion(BOARDID0, jsxCode, allowInputEntry=true);
</jsxgraph>
Is it possible that the problem is caused because the board ids are not properly handed over in
new JSXQuestion(BOARDID0, jsxCode, allowInputEntry=true);
Besides this problem, I would like to understand JSXGraph a bit better:
Is it somehow possible to arrange multiple boards with respect to each other? That is, above, below, right aligned, centered etc.
Does it make a difference whether boards are initialized as ‘const’ or ‘var’?
Is it necessary/beneficial/wrong to suspend and unsuspend board updates in the example above case?
Are the manual update commands in the code necessary/beneficial/useless?
Are there any obvious lapses in my coding or usage of JSXGraph?

In fact, it is correct that our filter in combination with formulas does not work correctly with multiple boards. At the moment, only one board ID is transferred to the JSXQuestion object and thus and thus it (and formulas) does not know anything about the second board. That is also one of the problems your example raises.
In addition, the boards actually have to be initialized with the JSXQuestion.initBoard() method for the bindInput() method to work. In the end, that's the root problem why your example doesn't work.
I will dedicate myself to this issue after the Christmas holidays and will be releasing a new version of the Moodle filter in January. Maybe there will be something new from JSXGraph by then, too.
Unfortunately, I can't offer you a dirty hack until then, as it requires a few basic changes to the filter.
I hope to be able to tell you more in January.
Have a nice Christmas and stay healthy!
Andreas

I now had the time to look at your problem and I was able to expand the Moodle filter. As of the new version v1.1.0-for3.10, several boards are also supported in formulas. You can find detailed instructions on how to use it and what to consider here on GitHub.
The new version of the plugin can be downloaded in the Plugins Directory.
I took the liberty of modifying your example from above and it works for me:
<jsxgraph width="400" height="300" numberOfBoards="2" ext_formulas>
// JavaScript code to create the construction.
var jsxCode = function (question) {
// Import final coordinates after submission
var x0={x0};
var t1,t2,t3 , v1,v2,v3 , x1,x2,x3;
[t1,t2,t3 , v1,v2,v3 , x1,x2,x3] =
question.getAllValues([1,2,3 , 1,2,3 , x0,x0,x0 ]);
JXG.Options.point.infoboxDigits = 1;
JXG.Options.point.snapSizeX = 1;
JXG.Options.point.snapSizeY = 0.1;
// Create boards
var brds = question.initBoards( [
{ // attribs for BOARDID0
boundingbox: [-1, 11, 12, -11], axis:true,
defaultAxes: {
x: {withLabel: true, name: 't in s',
label: {position: 'rt', offset: [-0, 15], anchorX: 'right'} },
y: {withLabel:true, name: 'x in m',
label: {position: 'rt', offset: [+15, -0]} } },
showCopyright: false, showNavigation: false
},
{ // attribs for BOARDID1
boundingbox: [-1, 3.5, 12, -3.5], axis:true,
defaultAxes: {
x: {withLabel: true, name: 't in s',
label: {position: 'rt', offset: [-0, 15], anchorX: 'right'} },
y: {withLabel:true, name: 'v_x in m/s',
label: {position: 'rt', offset: [+15, -0]} } },
showCopyright: false, showNavigation: false
}
] );
var brd0 = brds[0];
var brd1 = brds[1];
console.log(brd0, brd1);
// Board brd0 needs to be updated when changes in brd1 occur
question.addChildsAsc();
/* not needed anymore
brd1.addChild(brd0);
*/
// Attributes for points and lines
function attrPfix(addAttr={}) {
const attr = {fixed: true, visible: false, withLabel: false};
return { ...attr, ...addAttr};
}
function attrPmov(addAttr={}) {
const attr = {fixed: question.isSolved, snapToGrid: true, withLabel: false};
return { ...attr, ...addAttr};
}
function attrPsma(addAttr={}) {
const attr = {visible: true, withLabel: false, color:'#4285F4', size: 1};
return { ...attr, ...addAttr};
}
const attrLine = {borders: {strokeColor:'#4285F4', strokeWidth: 3} };
const attrGlid = {visible:false};
// Define lines and points on brd1
brd1.suspendUpdate();
var lV0 = brd1.create('segment', [[0,-10], [0,10]], {visible:false}),
lV3 = brd1.create('segment', [[-10,0], [20,0]], {visible:false});
var pV0 = brd1.create('glider', [0, v1, lV0], attrPmov({name: "pV0"}) ),
pV1 = brd1.create('point', [t1, v2], attrPmov({name: "pV1"}) ),
pV2 = brd1.create('point', [t2, v3], attrPmov({name: "pV2"}) ),
pV3 = brd1.create('glider', [t3, 0, lV3], attrPmov({name: "pV3"}) ),
pV01 = brd1.create('point', ["X(pV1)", "Y(pV0)"], attrPsma() ),
pV12 = brd1.create('point', ["X(pV2)", "Y(pV1)"], attrPsma() ),
pV23 = brd1.create('point', ["X(pV3)", "Y(pV2)"], attrPsma() ) ;
brd1.create('polygonalchain', [ pV0, pV01, pV1, pV12, pV2, pV23, pV3 ], attrLine);
brd1.unsuspendUpdate();
// Define lines and points on brd1
// Q: Is it necessary/beneficial/wrong to suspendUpdate here?
// A: It can be beneficial if you use a lot of objects. In this case the benefit is not worth mentioning, I think.
brd0.suspendUpdate();
var lX1 = brd0.create('line', [[function(){return pV1.X();},-10], [function(){return pV1.X();},10]], attrGlid),
lX2 = brd0.create('line', [[function(){return pV2.X();},-10], [function(){return pV2.X();},10]], attrGlid),
lX3 = brd0.create('line', [[function(){return pV3.X();},-10], [function(){return pV3.X();},10]], attrGlid);
var pX0 = brd0.create('point', [0, x0], attrPsma({fixed: true}) ),
pX1 = brd0.create('glider', [t1, x1, lX1], attrPmov({face: 'diamond'}) ),
pX2 = brd0.create('glider', [t2, x2, lX2], attrPmov({face: 'diamond'}) ),
pX3 = brd0.create('glider', [t3, x3, lX3], attrPmov({face: 'diamond'}) );
brd0.create('polygonalchain', [ pX0, pX1, pX2, pX3 ], attrLine);
brd0.unsuspendUpdate();
// Q: Are these updates necessary?
/* not with the new version
brd0.update();
brd1.update();
*/
/* not necessary anymore
question.board = brd0;
*/
// Whenever the construction is altered the values of the points are sent to formulas.
question.bindInput(0, () => { return pV1.X(); });
question.bindInput(1, () => { return pV2.X(); }); // typo here
question.bindInput(2, () => { return pV3.X(); });
question.bindInput(3, () => { return pV1.Y(); });
question.bindInput(4, () => { return pV2.Y(); });
question.bindInput(5, () => { return pV3.Y(); }); // typo here
question.bindInput(6, () => { return pX1.Y(); });
question.bindInput(7, () => { return pX2.Y(); });
question.bindInput(8, () => { return pX3.Y(); });
};
// Execute the JavaScript code.
new JSXQuestion(BOARDIDS, jsxCode, allowInputEntry=true); // use BOARDIDS here!!
</jsxgraph>
I've already answered the other questions in the code.
I hope I could help you!
Greetings, Andreas

Just for completeness sake, I post my final version of the JSXGraph code for a Formulas question based on Andreas' solution. My final touches were to
make the axis labels use LaTeX,
use the event handler .on('drag', ...) for two-way updates between the diagrams instead of using question.addChildsAsc().
Here is the final code:
<jsxgraph width="400" height="300" numberOfBoards="2" ext_formulas>
// JavaScript code to create the construction.
var jsxCode = function (question) {
// Import final coordinates after submission
var x0={x0};
var t1,t2,t3 , v1,v2,v3 , x1,x2,x3;
[t1,t2,t3 , v1,v2,v3 , x1,x2,x3] =
question.getAllValues([1,2,3 , 1,2,3 , x0,x0,x0 ]);
JXG.Options.point.infoboxDigits = 1;
JXG.Options.point.snapSizeX = 1;
JXG.Options.point.snapSizeY = 0.1;
// Attributes for points and lines
function attrPfix(addAttr={}) {
const attr = {fixed: true, visible: false, withLabel: false};
return { ...attr, ...addAttr};
}
function attrPmov(addAttr={}) {
const attr = {fixed: question.isSolved, snapToGrid: true, withLabel: false};
return { ...attr, ...addAttr};
}
function attrPsma(addAttr={}) {
const attr = {visible: true, withLabel: false, color:'#4285F4', size: 1};
return { ...attr, ...addAttr};
}
const attrLine = {borders: {strokeColor:'#4285F4', strokeWidth: 3} };
const attrGlid = {visible:false};
// Create boards
var brds = question.initBoards( [
{ // attribs for BOARDID0
boundingbox: [-1, 12, 13, -11], axis:true,
defaultAxes: {
x: {withLabel: true, name: '$$t\\;\\mathrm{(s)}$$',
label: {position: 'rt', offset: [10, 26], anchorX: 'right', parse: false, fontSize: 12 } },
y: {withLabel:true, name: '$$x\\;\\mathrm{(m)}$$',
label: {position: 'rt', offset: [10, 15], parse: false, fontSize: 12 } } },
zoom: {enabled:false, wheel: false}, pan: {enabled:false, needTwoFingers: false},
showCopyright: false, showNavigation: false
},
{ // attribs for BOARDID1
boundingbox: [-1, 3.8, 13, -3.5], axis:true,
defaultAxes: {
x: {withLabel: true, name: '$$t\\;\\mathrm{(s)}$$',
label: {position: 'rt', offset: [10, 26], anchorX: 'right', parse: false, fontSize: 12 } },
y: {withLabel:true, name: '$$v_x\\;\\mathrm{(m/s)}$$',
label: {position: 'rt', offset: [10, 15], parse: false, fontSize: 12 } } },
zoom: {enabled:false, wheel: false}, pan: {enabled:false, needTwoFingers: false},
showCopyright: false, showNavigation: false
}
] );
var brd0 = brds[0];
var brd1 = brds[1];
// console.log(brd0, brd1);
// Board brd0 needs to be updated when changes in brd1 occur
// question.addChildsAsc();
// Define lines and points on brd1
var pV1 = brd1.create('point', [t1, v1], attrPmov({name: "pV1"}) ),
pV2 = brd1.create('point', [t2, v2], attrPmov({name: "pV2"}) ),
pV3 = brd1.create('point', [t3, v3], attrPmov({name: "pV3"}) ),
pV01 = brd1.create('point', [0, "Y(pV1)"], attrPsma() ),
pV12 = brd1.create('point', ["X(pV1)", "Y(pV2)"], attrPsma() ),
pV23 = brd1.create('point', ["X(pV2)", "Y(pV3)"], attrPsma() ) ;
pV34 = brd1.create('point', ["X(pV3)", 0], attrPsma() ) ;
brd1.create('polygonalchain', [ pV01, pV1, pV12, pV2, pV23, pV3, pV34 ], attrLine);
// Define lines and points on brd0
var pX0 = brd0.create('point', [0, x0], attrPsma({fixed: true}) ),
pX1 = brd0.create('point', [t1, x1], attrPmov({face: 'diamond'}) ),
pX2 = brd0.create('point', [t2, x2], attrPmov({face: 'diamond'}) ),
pX3 = brd0.create('point', [t3, x3], attrPmov({face: 'diamond'}) );
brd0.create('polygonalchain', [ pX0, pX1, pX2, pX3 ], attrLine);
// Define dependencies
pV1.on('drag', function() { pX1.moveTo([this.X(), pX1.Y()], 0); });
pV2.on('drag', function() { pX2.moveTo([this.X(), pX2.Y()], 0); });
pV3.on('drag', function() { pX3.moveTo([this.X(), pX3.Y()], 0); });
pX1.on('drag', function() { pV1.moveTo([this.X(), pV1.Y()], 0); });
pX2.on('drag', function() { pV2.moveTo([this.X(), pV2.Y()], 0); });
pX3.on('drag', function() { pV3.moveTo([this.X(), pV3.Y()], 0); });
// Whenever the construction is altered the values of the points are sent to formulas.
question.bindInput(0, () => { return pV1.X(); });
question.bindInput(1, () => { return pV2.X(); });
question.bindInput(2, () => { return pV3.X(); });
question.bindInput(3, () => { return pV1.Y(); });
question.bindInput(4, () => { return pV2.Y(); });
question.bindInput(5, () => { return pV3.Y(); });
question.bindInput(6, () => { return pX1.Y(); });
question.bindInput(7, () => { return pX2.Y(); });
question.bindInput(8, () => { return pX3.Y(); });
};
// Execute the JavaScript code.
new JSXQuestion(BOARDIDS, jsxCode, allowInputEntry=false); // use BOARDIDS here!!
</jsxgraph>

Related

How do I set/add DRACOLoader and KTX2Loader to tb.loadObject in threebox-plugin?

I am trying to load a model that is Draco compressed and I am getting this error:
Error: THREE.GLTFLoader: No DRACOLoader instance provided.
How do I set the Draco loader (or enable) in threebox-plugin?
id: item3d.name,
type: 'custom',
renderingMode: '3d',
onAdd: (map, mbxContext) => {
const options = {
obj: item3d.modelPath,
type: 'gltf',
scale: item3d.scale,
units: 'meters',
rotation: { x: 90, y: 0, z: 0 }
}
const ktx2Loader = new KTX2Loader();
ktx2Loader.setTranscoderPath('assets/basis/');
ktx2Loader.detectSupport( window['tb'].renderer );
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('assets/draco/');
// const gltfLoader = new GLTFLoader();
// gltfLoader.setDRACOLoader(dracoLoader);
// gltfLoader.setKTX2Loader(ktx2Loader);
window['tb'].loadObj(options, (model) => {
let loadedItem = model.setCoords([item3d.coordinates['lng'], item3d.coordinates['lat']]);
loadedItem.castShadow = true;
window['tb'].add(loadedItem);
})
},
render: function(gl, matrix) {
}
}```

Chart beginAtZero: true, min:0 and title:'minutes' doesn't work

I want the axis Y from my react-chart starts from 0, I have been trying many solutions, like beginAtZero: true and min:0 but is doesn't work.
import React, { Component } from 'react';
// Documentation https://www.npmjs.com/package/react-charts
import { Chart } from 'react-charts';
I would like also to add title to axis X title:minutes doesn't work either, here is the code:
MyChart = () => {
this.componentRef = React.createRef();
if (this.state.selectedDocumentContent !== undefined) {
const content = this.state.selectedDocumentContent;
const firstTimeStamp = this.toTimestampSeconds(content.time);
const metricKeys = Object.keys(content.metrics);
var data = [];
for (var i = 1; i < metricKeys.length-1; i++) {
const entries = content.metrics[metricKeys[i]];
var metricData = [];
for (var j = 0; j < entries.length; j++) {
let entry = entries[j];
let timestamp = this.toTimestampSeconds(entry.time);
metricData.push([timestamp - firstTimeStamp, entry.value]);
}
data.push({ label: metricKeys[i], data: metricData })
}
const axes = [
{ primary: true, type: 'linear', position: 'bottom', title: 'minutes' }, //x
{ type: 'linear', position: 'left', options: {
scales: {
yAxes: [{
display: true,
ticks: {
beginAtZero: true,
min:0
}
}]
}} //y
}
];
const lineChart = (
// A react-chart hyper-responsively and continuously fills the available
// space of its parent element automatically
<div
style={{
margin: '30px',
width: '600px',
height: '400px'
}}
>
<Chart data={data} axes={axes} tooltip/>
</div>
)
return lineChart;
}
}
I am using this chart: https://www.npmjs.com/package/react-charts
"react-charts": "^2.0.0-beta.7".
Fixed! I almost got crazy about this, I solved it with base:0.
const axes = [
{ primary: true, type: 'linear', position: 'bottom'}, //x
{ type: 'linear', position: 'left', base: 0} //y
];
Still did not fix the issue with the title... continuing the investigation....
Does anybody knows about how to set a maximum value? (max: X, does not work)

Openlayers 6 : How to change PointStyle when selected?

I use Openlayers 6 try to change Point style when click on a point previously created but this code select the Point but no new style is applied. What Am i missing ?
I send the function to create the new Style, and the code to insert a Select Interaction
const styleClick = function(feature, resolution) {
return [
new Style({
image: new Circle({
fill: Fill({ color: [245, 121, 0, 0.8] }),
stroke: Stroke({ color: [0,0,0,1] }),
radius: 7
}),
text: new Text({
font: '24px Verdana',
text: feature.get('name'),
offsetY: 20,
fill: Fill({
color: [255, 0, 0, 0.8]
})
})
})
];
};
let selectSingleClick = new Select({style: styleClick});
map.addInteraction(selectSingleClick);
selectSingleClick.on('select', function (e) {
if( e.target.getFeatures().getLength() > 0 )
{
document.getElementById('obj-select').innerHTML =
' ' +
e.target.getFeatures().item(0).getGeometry().getType() +
' color is ' +
e.target.getFeatures().item(0).getStyle() +
' and deselected ' +
e.deselected.length +
' features)'
if( e.target.getFeatures().item(0).getGeometry().getType() == "LineString" )
{
//do something
};
if( e.target.getFeatures().item(0).getGeometry().getType() == "Point" )
{
//do something
};
//getGeometry().getCoordinates();
}
map.removeInteraction(selectSingleClick);
}
);
}
}
First, add your custom style like this:
let selectStyle = function (feature) {
let fill = new ol.style.Fill({
color: '#326de5',
});
let stroke = new ol.style.Stroke({
color: '#a30f4f',
width: 1.5
});
let styles = [
new ol.style.Style({
image: new ol.style.Circle({
fill: fill,
stroke: stroke,
radius: 6
}),
fill: fill,
stroke: stroke
})
];
return styles;
}
Now add top style to Select like this:
let selectInteraction = new ol.interaction.Select({
style: selectStyle
});
map.addInteraction(selectInteraction);
This code works for me!

Custom styling Google bar chart

Is there a possibility to accomplish a Google barchart to look like this?
The end of each bar with custom styling
Annotation comes below the line (GOAL 10.3)
you can use the chart layout method to add an icon, or any element, to the end of the bar.
// add icon to bar
var barBounds = layout.getBoundingBox('bar#0#0');
var icon = chart.getContainer().appendChild(document.createElement('span'));
icon.className = 'icon';
icon.style.top = (barBounds.top + containerBounds.top - 3) + 'px';
icon.style.left = (barBounds.left + containerBounds.left + (barBounds.width) - 24) + 'px';
icon.innerHTML = '<i class="fas fa-arrow-alt-circle-right"></i>';
also, instead of drawing the annotation and trying to prevent the chart from moving it,
we can leave it out and add our own custom annotation...
// add annotation
var labelCopy = svg.getElementsByTagName('text')[0];
var annotation = labelCopy.cloneNode(true);
svg.appendChild(annotation);
annotation.setAttribute('text-anchor', 'middle');
annotation.textContent = data.getValue(0, data.getNumberOfColumns() -1);
annotation.setAttribute('x', xLoc);
annotation.setAttribute('y',
layout.getYLocation(0) + (parseInt(annotation.getAttribute('font-size')) * 3)
);
see following working snippet...
google.charts.load('current', {
packages: ['corechart']
}).then(drawHorizontalChart_portal_name_stella_york_horz_month_points);
function drawHorizontalChart_portal_name_stella_york_horz_month_points() {
var data = google.visualization.arrayToDataTable([
["", "Goal Achieved", {role: 'style'}, "GOAL 13.1 points", {role: 'style'}, {role: 'annotation'}],
[1, 1.5, "opacity: .75;", 13.1, "opacity: 0;", "GOAL 13.1 points"]
]);
var view = new google.visualization.DataView(data);
view.setColumns([0, 1, 3, 4]);
var options = {
title: '',
width: '100%',
height: 132,
chartArea: {
height: '100%',
width: '100%',
top: 36,
left: 18,
right: 18,
bottom: 48
},
hAxis: {
title: '',
minValue: 0,
gridlines: {
count: 6
},
format: '0'
},
bar: {
groupWidth: "60%"
},
legend: {
position: "top"
},
series: {
0: {
color: '#70b5c5',
visibleInLegend: false
}, // Goal Achieved
1: {
color: '#000000',
type: 'line',
annotations: {
textStyle: {
color: '#000000'
},
stem: {
color: 'transparent',
length: -128
},
vertical: true
}
} // Target Goal
},
vAxis: {
gridlines: {
color: 'transparent'
},
ticks: [{v: 1, f: ''}]
}
};
var chart = new google.visualization.BarChart(document.getElementById("portal-name-stella-york-horz-month-points"));
google.visualization.events.addListener(chart, 'click', function(e) {
console.log(JSON.stringify(e));
});
google.visualization.events.addListener(chart, 'ready', function () {
// init variables
var layout = chart.getChartLayoutInterface();
var containerBounds = chart.getContainer().getBoundingClientRect();
var svg = chart.getContainer().getElementsByTagName('svg')[0];
var svgNS = svg.namespaceURI;
var xLoc = drawVAxisLine(chart, layout, data.getValue(0, 3));
// add image to bar
var barBounds = layout.getBoundingBox('bar#0#0');
var icon = chart.getContainer().appendChild(document.createElement('span'));
icon.className = 'icon';
icon.style.top = (barBounds.top + containerBounds.top - 3) + 'px';
icon.style.left = (barBounds.left + containerBounds.left + (barBounds.width) - 24) + 'px';
icon.innerHTML = '<i class="fas fa-arrow-alt-circle-right"></i>';
// add annotation
var labelCopy = svg.getElementsByTagName('text')[0];
var annotation = labelCopy.cloneNode(true);
svg.appendChild(annotation);
annotation.setAttribute('text-anchor', 'middle');
annotation.textContent = data.getValue(0, data.getNumberOfColumns() -1);
annotation.setAttribute('x', xLoc);
annotation.setAttribute('y',
layout.getYLocation(0) + (parseInt(annotation.getAttribute('font-size')) * 3)
);
});
chart.draw(view, options);
}
jQuery(window).resize(function() {
drawHorizontalChart_portal_name_stella_york_horz_month_points();
});
function drawVAxisLine(chart, layout, value) {
var chartArea = layout.getChartAreaBoundingBox();
var svg = chart.getContainer().getElementsByTagName('svg')[0];
var xLoc = layout.getXLocation(value)
svg.appendChild(createLine(xLoc, chartArea.top + chartArea.height, xLoc, chartArea.top, '#000000', 2)); // axis line
return xLoc;
}
function createLine(x1, y1, x2, y2, color, w) {
var line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line.setAttribute('x1', x1);
line.setAttribute('y1', y1);
line.setAttribute('x2', x2);
line.setAttribute('y2', y2);
line.setAttribute('stroke', color);
line.setAttribute('stroke-width', w);
return line;
}
.icon {
font-size: 32px;
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://www.gstatic.com/charts/loader.js"></script>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css" integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" crossorigin="anonymous">
<div id="portal-name-stella-york-horz-month-points"></div>

Openlayer Linestring with two or more lines on the one or more coordinates?

My problem is the following:
Linestring with two or more lines on the one or more coordinates.
With several LineStrings only one string is shown in the map, I know, the LineStrings also use the same coordinates.
Is there a way that the LineStrings can use one and the same coordinates, but a shift of the line (to have for example three lines next to each other) takes place in the structure of the map?
The code is a bit better, which explains a lot:
var logoElement = document.createElement ('a');
logoElement.href = 'http://www.schienenpost.de/';
logoElement.target = '_blank';
var logoImage = document.createElement ('img');
logoImage.src = 'http://www.schienenpost.de/schienenpost.png';
logoElement.appendChild (logoImage);
var iconStyle = new ol.style.Style ({
image: new ol.style.Icon (/** #type {olx.style.IconOptions} */ ({
anchor: [0.5, 1.0],
src: 'http://www.schienenpost.de/marker/marker.png'
}))
});
var scaleLineControl = new ol.control.ScaleLine();
var markerStartEnd = function (layer,feature) {
var i, iconFeature, iconFeatures = [], coordinates, nameLine;
coordinates = feature.getGeometry ().getCoordinates ();
nameLine = feature.getProperties () ['name'];
i = 0;
iconFeature = new ol.Feature ({
geometry: new ol.geom.Point (coordinates[i]),
name: 'Start'+nameLine,
});
iconFeature.setStyle (iconStyle);
iconFeatures.push (iconFeature);
i = coordinates.length - 1;
iconFeature = new ol.Feature ({
geometry: new ol.geom.Point (coordinates[i]),
name: 'End'+nameLine,
});
iconFeature.setStyle (iconStyle);
iconFeatures.push (iconFeature);
layer.getSource ().addFeatures (iconFeatures);
};
var layerLines = new ol.layer.Vector ({
source: new ol.source.Vector ({
format: new ol.format.GeoJSON (),
url: 'schienenpost.geojson',
useSpatialIndex: false
}),
style: new ol.style.Style ({stroke: new ol.style.Stroke ({color : 'red', width: 3})}),
});
var layerMarker = new ol.layer.Vector ({
title: 'Marker',
source: new ol.source.Vector ()
});
var element = document.getElementById ('popup');
var popup = new ol.Overlay ({
element: element,
positioning: 'bottom-center',
stopEvent: false,
offset: [0, -0]
});
var map = new ol.Map ({
controls: ol.control.defaults ()
.extend ([
new ol.control.OverviewMap (),
new ol.control.FullScreen (),
scaleLineControl
]),
//target 'map',
target: document.getElementById ('map'),
layers: [
new ol.layer.Tile ({
source: new ol.source.OSM ()
}),
],
view: new ol.View ({
center: ol.proj.fromLonLat ([10.627, 53.620]),
zoom: 8
}),
logo: logoElement
});
map.addOverlay (popup);
map.addLayer (layerLines);
map.addLayer (layerMarker);
map.once ('moveend', function(e) {
layerLines.getSource ().getFeaturesCollection ().forEach (function (feature) {
markerStartEnd (layerMarker,feature);
});
});
map.on ('click', function (evt) {
var feature = map.forEachFeatureAtPixel (evt.pixel,
function (feature) {
return feature;
});
if (feature) {
var coordinates = feature.getGeometry ().getCoordinates ();
var clickpoint = map.getCoordinateFromPixel (evt.pixel);
if (!isNaN (coordinates [0])) { // Punkt
popup.setPosition (coordinates);
} else if (!isNaN (coordinates [0][0])) { // Linie
popup.setPosition (clickpoint);
} else { // kein brauchbares feature
$ (element).popover ('destroy');
return;
}
$ (element).popover ({
'placement': 'top',
'html': true,
'content': feature.get ('name')
});
$ (element).popover ().data ('bs.popover').options.content = feature.get ('name');
$ (element).popover ('show');
} else {
$ (element).popover ('hide');
}
});
map.on ('pointermove', function(e) {
if (e.dragging) {
$ (element).popover ('destroy');
return;
}
var pixel = map.getEventPixel (e.originalEvent);
var hit = map.hasFeatureAtPixel (pixel);
map.getTarget ().style.cursor = hit ? 'pointer' : '';
});